《UNIX网络编程 卷1》 笔记: 互斥锁与条件变量
2017-06-01 23:40
141 查看
考虑下两个线程同时对同一个全局变量i(初始值为0)进行自增操作(i++)会发生什么情况。C编译器将自增运算符转换成三条机器指令:从内存装载到寄存器、递增寄存器、从寄存器存储到内存。可能发生如下情况:
1. 线程A运行,把变量i的值0装载到一个寄存器。
2. 内核调度模块决定运行线程B,此时线程A的寄存器被保存,B的寄存器被恢复。
3. 线程B执行完自增操作的三条指令后,把新值1存储到变量i。
4. 内核调度模块调度线程A运行,线程A的寄存器被恢复,从恢复前执行指令的地方(第二条指令)继续执行,把寄存器的值递增为1,然后存储到变量i。
最终变量i的值是1而不是2,这是错误运行的结果。
这个问题也就是我们上节说的“同步”问题。出现这种问题的原因是因为多个线程可以并发地(或并行地)运行且访问相同的变量。
注意:并发和并行是两个不同的概念。并发是指两个线程同时在运行,而并行是指两线程的运行时间有重叠(比如说在单CPU系统上,有两个线程A和B需要运行3秒钟,A先运行2秒钟,然后B运行3秒钟结束,最后A运行1秒钟结束,我们说A和B是并行地运行)。
为了解决这个问题,我们使用互斥锁保护这个全局变量:多个线程要访问这个变量,必须先获取互斥锁,如果锁被其他线程占用,则只能阻塞等待锁被释放。变量访问之后要释放锁。互斥锁的数据类型是pthread_mutex_t,linux提供的互斥锁API如下:
使用互斥锁保护全局变量i的代码如下:
互斥锁用于防止多个线程同时访问某个变量,但我们还需要在等待某个条件(事件)发生时能让我们进入睡眠的机制。如果没有这个机制,线程在等待一个条件发生期间只能轮询,这显然非常浪费CPU资源。条件变量加上互斥锁就能实现这种机制。条件变量的类型是pthread_cond_t,linux提供的条件变量API如下:
条件变量总是和一个表示“条件”的全局变量关联,在此例中即是flag变量,flag值为0表明条件不成立(事件还未发生)。全局变量总是需要互斥锁保护,因此互斥锁和条件变量经常一起使用。这也解释了为什么pthread_cond_wait函数的两个参数一个是条件变量一个是互斥锁。
另外,由于测试条件之前总是先加锁,所以当条件不成立时pthread_cond_wait函数必须先解锁,然后把调用线程投入睡眠。当线程被唤醒时,它又再次加锁,然后返回。
下一节我们使用多线程实现web客户程序。
1. 线程A运行,把变量i的值0装载到一个寄存器。
2. 内核调度模块决定运行线程B,此时线程A的寄存器被保存,B的寄存器被恢复。
3. 线程B执行完自增操作的三条指令后,把新值1存储到变量i。
4. 内核调度模块调度线程A运行,线程A的寄存器被恢复,从恢复前执行指令的地方(第二条指令)继续执行,把寄存器的值递增为1,然后存储到变量i。
最终变量i的值是1而不是2,这是错误运行的结果。
这个问题也就是我们上节说的“同步”问题。出现这种问题的原因是因为多个线程可以并发地(或并行地)运行且访问相同的变量。
注意:并发和并行是两个不同的概念。并发是指两个线程同时在运行,而并行是指两线程的运行时间有重叠(比如说在单CPU系统上,有两个线程A和B需要运行3秒钟,A先运行2秒钟,然后B运行3秒钟结束,最后A运行1秒钟结束,我们说A和B是并行地运行)。
为了解决这个问题,我们使用互斥锁保护这个全局变量:多个线程要访问这个变量,必须先获取互斥锁,如果锁被其他线程占用,则只能阻塞等待锁被释放。变量访问之后要释放锁。互斥锁的数据类型是pthread_mutex_t,linux提供的互斥锁API如下:
#include <pthread.h> /*获取锁*/ int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); /*释放锁*/ int pthread_mutex_unlock(pthread_mutex_t *mutex);pthread_mutex_trylock函数与pthread_mutex_lock函数的区别是pthread_mutex_trylock函数获取不到锁时立即返回而不阻塞。
使用互斥锁保护全局变量i的代码如下:
pthread_mutex_t lock; pthread_mutex_lock(&lock); i++; Pthread_mutex_unlock(&lock);
互斥锁用于防止多个线程同时访问某个变量,但我们还需要在等待某个条件(事件)发生时能让我们进入睡眠的机制。如果没有这个机制,线程在等待一个条件发生期间只能轮询,这显然非常浪费CPU资源。条件变量加上互斥锁就能实现这种机制。条件变量的类型是pthread_cond_t,linux提供的条件变量API如下:
#include <pthread.h> /*等待一个条件变量,线程进入睡眠状态*/ int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); /*唤醒等待在条件变量上的一个线程*/ int pthread_cond_signal(pthread_cond_t *cond); /*唤醒等待在条件变量上的所有线程*/ int pthread_cond_broadcast(pthread_cond_t *cond);举个例子,我们使用一个全局变量flag标志一个事件是否发生。线程A测试flag如果为0,表明事件未发生则睡眠等待。线程B产生这个事件然后将flag标志置1,唤醒线程A。为此我们定义了以下三个变量:
int flag; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER;线程A使用如下的代码等待事件发生:
pthread_mutex_lock(&mutex); while (flag == 0) pthread_cond_wait(&cond, &mutex); /*睡眠等待事件发生*/ /*下一步的动作*/ pthread_mutex_unlock(&mutex);线程B使用如下的代码产生事件并唤醒线程A:
/*某个事件发生*/ pthread_mutex_lock(&mutex); flag = 1; pthread_cond_signal(&cond); /*唤醒线程A*/ pthread_mutex_unlock(&mutex);
条件变量总是和一个表示“条件”的全局变量关联,在此例中即是flag变量,flag值为0表明条件不成立(事件还未发生)。全局变量总是需要互斥锁保护,因此互斥锁和条件变量经常一起使用。这也解释了为什么pthread_cond_wait函数的两个参数一个是条件变量一个是互斥锁。
另外,由于测试条件之前总是先加锁,所以当条件不成立时pthread_cond_wait函数必须先解锁,然后把调用线程投入睡眠。当线程被唤醒时,它又再次加锁,然后返回。
下一节我们使用多线程实现web客户程序。
相关文章推荐
- 《UNIX网络编程 卷2》 笔记: 互斥锁与条件变量
- UNIX网络编程(七)互斥锁和条件变量
- UNIX网络编程卷二 笔记 互斥锁和条件变量
- IPC同步 笔记之 互斥锁与条件变量
- 进程间通信笔记(5)—互斥锁和条件变量
- Linux进程间通信IPC学习笔记之同步一(线程、互斥锁和条件变量)
- 利用POSIX互斥锁和条件变量实现的信号量
- 《UNIX网络编程 》学习笔记 (一)
- 互斥锁和条件变量
- Unix网络编程笔记(第四章)
- linux 多线程编程 互斥锁与条件变量
- 【Linux C 多线程编程】互斥锁与条件变量
- 转 互斥锁和条件变量
- 线程同步-条件变量-unix网络编程
- 《UNIX网络编程 》学习笔记 (五)
- unix网络编程笔记(第一章)
- 进程间的通信(互斥锁、条件变量、读写锁、文件锁、信号灯)
- unix网络编程笔记
- IO特性(1): socket相关的几个高级IO函数(Unix网络编程笔记)
- 【转】 进程间的通信(互斥锁、条件变量、读写锁、文件锁、信号灯)