一、线程间互斥
1.互斥锁
1) 引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性;
2) 互斥锁主要用来保护临界资源;
3) 每个临界资源都由一个互斥锁来保护,任何时刻多只能有一个线程能访问该资源;
4) 线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
2.互斥锁API:
设置锁:初始化锁
第一个参数是变量的地址,
第二个参数是互斥锁的属性。
上锁:
解锁:
拓展:pthread_mutex_destroy 删除锁。
pthread_mutex_trylock()试图加锁函数。
3.示例代码
一定会将加锁与解锁中的所以的动作完成后,下一个申请才会开始。保证数据的原子性。
比如两个线程同时操作日志文件。读写操作的结果可能被打断,结果是未知的。
互斥锁过程回顾:
定义锁变量 //买一把锁,(全局变量)
初始化锁 //设置锁状态
对临界资源加锁 //上锁
临界资源代码
对临界资源解锁 //解锁
二、同步与信号量
1.什么是同步
互斥表示的是两者不能同时对临界资源进行操作,先后顺序没有任何保证,哪个线程拥有时间片,哪个线程就进行临界资源访问。相当于两个操作之间是相互排斥的,必须保证一个线程访问结束,另一个线程才可以进行访问资源。(访问互斥,无序,当一个线程加锁之后,必须等当前线程的临界资源访问代码执行结束之后,执行了解锁代码,才会释放了锁!)
同步也是不可以同时运行(同时访问临界资源),同步是在互斥的基础上按照一定的顺序进程访问资源。同步在互斥的基础上,加入一定的机制保证资源访问的顺序。(访问互斥,有序)。
2.信号量的提出
实现同步的功能提出信号量的概念。在现实生活中,交通路口的红绿灯就是一种同步,有红绿两种等,红灯亮,则绿灯灭。红灯灭,则绿灯亮。
红灯状态: 1 --> 0
绿灯状态: 0 --> 1
信号量表示资源的数量,当绿灯状态为1,表示绿色方向的道路资源可用,可以通行后,绿灯再减1变成0。
资源的数量可以是多个,即信号量的数量可以大于1,比如公司PC的数量就是一种资源,可以有N台PC供员工申请试用,申请成功就要将资源的数量减1,当有员工返还PC时,资源的数据加1。
P/V操作:
3.有名信号与无名信号的比较
1) 有名信号量必须指定一个相关联的文件名称,这个name通常是文件系统中的某个文件;无名信号量不需要指定名称。
2) 有名信号量既可用于线程间的同步,又能用于进程间的同步;无名信号量通过shared参数来决定是进程内还是相关进程间共享。
3) 有名信号量是随内核持续的,一个进程创建一个信号量,另外的进程可以通过该信号量的外部名(创建信号量使用的文件名)来访问它。进程结束后,信号量还存在,并且信号量的值也不会改动。
4) 无名信号量的持续性却是不定的:如果无名信号量是由单个进程内的各个线程共享的,那么该信号量就是随进程持续的,当该进程终止时它也会消失。如果某个无名信号量是在不同进程间同步的,该信号量必须存放在共享内存区中,只要该共享内存区存在,该信号量就存在。
5) 无名信号量(文件系统不可见,内存信号量)。
信号量是一个受保护的变量,不可以随意操作,由相应的函数操作。
4.pthreaad库常用的信号量操作函数如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem); //P操作
int sem_post(sem_t *sem); //V操作
int sem_trywait(sem_t *sem);
int sem_getvalue(sem_t *sem, int *svalue);
pshared: 信号量共享范围,0是线程间使用。非0表示进程间使用(不过函数并没有对进程间无名信号支持, 设想总是好的吗).
value: 资源的个数。
sem_wait() 信号量减一
sem_post() 信号量加一
sem_trywait() 不阻塞的方式P申请。
setm_getvalue() 获取当前信号量的个数,保存在第二个参数中。
5.示例代码
1) 信号量实现互斥:
2) 信号量实现同步
3) 锁实现同步:
PS:
1. 锁是信号量数量为1时的一种特殊情况,即锁也可以看成一种信号量。
2. 互斥的实现要用到一个锁变量或一个信号量。
3. 同步要用到至少两把锁或两个信号量。
思考:
如果三个线程,如果实现同步(即pthread1, pthread2 pthread3按照顺序执行)?