为什么需要线程同步?
确保共享数据在某个时间点由某个线程单独执行,而不是一起执行。
一.互斥锁
1.互斥锁定义
线程A访问共享资源步骤:
A对共享资源上锁-A访问共享资源-A释放锁;
此时共享资源没有被上锁,才能由其他线程进行访问,在A访问期间,其他线程将被阻塞直到A释放锁。
2. 互斥锁相关函数
首先是初始化一个锁,然后销毁一个锁,加锁解锁操作。
要和线程操作函数以及信号量操作函数区别起来。
3.pthread_mutex_init
pthread_mutex_trylock函数在线程尝试加锁失败时并不会阻塞,而是直接返回。
下图是多线程卖票程序回调函数:
在主线程开始和结束分别初始化和销毁了一个互斥锁。
然后创建了三个线程,分别将此回调函数作为参数传到线程创建函数中,接着用线程分离技术自动回收多余线程资源。
如图所示,用了while(1)循环处理逻辑,每次进入循环线程只会卖一张票,然后再去竞争锁。
二. 死锁
三. 读写锁
如图所示,读写锁可以单独控制加的读和写的锁,对于很多业务来说,读的操作较多,而写的操作较少,读写锁效率就较高。
示例:3个线程写,5个线程读
3个线程的回调函数使用写锁去上锁解锁,5个线程使用读锁去上锁解锁。
四. 生产者消费者模型
生产线程生产商品,放到共享内存中,消费线程从共享线程中取商品。
其中有个注意点:当生产线程生产足够多的商品时,应该通知消费线程去消费,而生产线程会阻塞;
当消费线程消费足够多的商品时,应该通知生产线程去生产,消费线程会暂时阻塞。
解决方法:条件变量和信号量。
共享内存存储数据可以用链表等数据结构。
五.条件变量
wait函数是等待,就是阻塞指定时长线程,而signal函数会唤醒一个正在等待的线程。broadcast唤醒所有的线程。
上图是消费者模型,当没有数据的时候,调用wait条件变量函数阻塞所有消费者线程,并自动释放锁,
然后生产者线程
会拿到锁,进行生产,每生产一个都会调用signal条件变量函数,他会唤醒正在阻塞中的消费线程去消费,
六. 信号量
疑问?有了互斥锁和条件变量,为什么还需要信号量,之前程序不是跑的很好吗?
sem_wait函数会使信号量减1,
sem_timeddwait当为0的时候会阻塞指定时长;
sem_post函数会使信号量加1。
sem_init函数pshared值为0则代表在线程间共享,不为0说明在进程间共享。
伪代码如下:
生产者信号量初始化为8,消费者信号量初始化为0;在生产者生产的时候会使生产者信号量减1,并使消费者信号量加1,消费者线程解除阻塞,
消费者信号量减1,并将生产者信号量加1。
就是说在此模型中,生产者不会一直生产下去,如果不消费,那我最多生产8个就停了。
是不是很熟悉,像资本社会一样,拼命生产,无人消费就会引发经济危机,招了那么多美女主播看似蒸蒸日上,结果大家没钱没观众,那不就破产了吗,程序员不就失业了嘛。
有信号量之后在生产者消费者模型中就不需要条件变量了。
如上图所示,消费者模型中不需要再判断有没有商品了,也不需要sleep函数进行控制竞争了。