多个线程同时运行,访问资源,不会导致程序的结果产生二义性
临界资源:在同一时刻,该资源只能被一个线程(执行流所访问)
访问:在临界区当中对临界资源进行非原子操作
使用互斥锁来保证互斥属性
互斥锁的底层是互斥量,互斥量本质上是一个计数器,该计数器只有两个取值0或者1
0代表,无法获取互斥锁,进而表示需要访问的临界资源不可以被访问
1代表,可以获取互斥锁,进而表示需要访问的临界资源可以被访问
加锁:对于互斥锁当中的互斥量保存的计数器进行减1操作
解锁:对于互斥锁当中的互斥量保存的计数器进行加1操作
加锁的时候将寄存器当中的值和内存当中的值进行互换,互换的指令为xchgb,而这种互换操作是原子性的,一步完成
当交换完毕之后,需要对寄存器当中的当中的值进行判断
如果寄存器当中的值为0,则表示不能进行加锁,意味着当前加锁操作会被阻塞,从而不能访问临界资源
如果寄存器当中的值为1,则表示可以加锁,对计数器进行减1操作之后,会得到锁资源,并且加锁操作返回,从而去访问临界资源。
当计数器的值为1的时候,表示可以加锁操作,当寄存器和内存的值进行一步交换之后,相当于给计数器进行减1操作了
当计数器的值为0的时候,表示不可以加锁,当寄存器和内存的值进行一步交换之后,并没有对计数器当中的值做到真实修改,当前的执行流就被挂起来等待
1.定义互斥锁:
互斥锁变量类型 pthread_mutex_t
2.初始化互斥锁
动态:需要进行销毁
int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr);
restrict mutex:互斥锁的变量,传参的时候传入互斥锁变量的地址
restrict attr :互斥锁的属性,一般情况下,我们不关心,采用默认的属性,传值为NULL就可以了
静态:不需要进行销毁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t 本身也是一个结构体类型, PTHREAD_MUTEX_INITIALIZER宏定义是一个结构体的值,使用这种初始方式可以直接填充 pthread_mutexattr_t 这个结构体
3.加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex:传入互斥锁变量的地址,来进行加锁操作
如果使用接口进行加锁操作的时候:
如果计数器当中的值为1,意味着可以加锁,加锁操作之后,对计数器当中的1改变成0
如果计数器当中的值为0,意味着不可以加锁,该接口阻塞等待,执行流就不会往下继续执行了
int pthread_mutex_trylock(pthread_mutex_t *mutex);
mutex:传入互斥锁变量的地址,来进行加锁操作
如果使用该接口进行加锁操作的时候:
如果计数器当中的值为1,意味着可以加锁,加锁操作之后,对计数器当中的1改变为0
如果计数器当中的值为0,意味着不可以加锁,该接口直接返回,不进行阻塞等待,返回为EBUSY(拿不到互斥锁)
一般在使用trylock这样的接口进行加锁的时候,一般操作那个循环加锁的方式,防止由于拿不到锁资源,而直接返回。从而可以从代码当中直接访问临界资源,从而导致程序产生二义性的结果
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
带有超时时间的加锁接口
restrict mutex:传入互斥锁变量的地址,来进行加锁操作
restrict abs_timeout :加锁超时间,当加锁的时候,超过加锁的超时时间之后,还没有获取的互斥锁,则报错返回,不进行阻塞等待,返回ETIMEOUT
struct timespec有两个变量,第一个变量代表秒,第二个变量代表纳秒
4.解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
不管是加锁操作的三个函数哪个进行的枷锁,都可以使用 pthread_mutex_unlock进行解锁
5.销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁销毁接口,如果使用互斥锁完成之后,不调用销毁接口,就会造成内存泄漏
注:
程序当中有一个执行流没有释放锁资源,会导致其他想要获取该互斥锁的执行流陷入阻塞等待,这种情况称之为死锁
程序当中,每一个执行流都占有一把互斥锁,但是由于各个执行流在占有互斥锁的情况下,还想申请其他的锁,这种情况下称之为死锁
同步实现的事情:
当有资源的时候,可以直接去获取资源,
没有资源的时候,线程进行等待,等待另外的线程生产一个资源,当生产完成一个资源的时候,通知等待的进程
动态:需要调用销毁接口
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
restrict cond:传入条件变量的变量的地址
restrict attr:条件变量的属性,一般设置为NULL,采用默认属性
静态:不调用销毁接口
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
restrict cond:传入条件变量的变量的地址
restrict mutex:传入互斥锁变量的地址
int pthread_cond_broadcast(pthread_cond_t *cond);
唤醒PCB等待队列当中所有的线程
int pthread_cond_signal(pthread_cond_t *cond);
唤醒至少一个PCB等待队列当中的线程
如果单纯的同步,不保证互斥,也就意味着不同的执行流可以在同一时刻去访问临界资源,所以在条件变量当中需要使用互斥锁来保证互斥属性,保证各个执行流在同一时刻访问临界资源的时候,只有一个执行流在访问。
条件变量当中的互斥锁也是同样的道理,保证了各个执行流在访问临界资源的时候,只有一个执行流可以访问。从条件变量的使用角度而言,完成了生产线程和消费线程之间的互斥。
以消费者生产者模型为例
防止消费者线程先释放互斥锁,被生产者线程抢在消费者线程进入PCB等待队列之前拿到互斥锁,并且执行生成逻辑后通知PCB等待队列,而这个时候,再将消费者线程放到PCB等待队列当中,就可能造成没有生产者线程再去调用pthread_cond_signal/broadcast这样的接口来唤醒PCB等待队列了