您的位置:首页 > 理论基础 > 计算机网络

《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如下:

#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客户程序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: