本文通过一个具体案例来说明什么是线程同步。

1. 问题提出

学生线程写作业,老师线程检查作业。要求:只有学生线程写完作业了,老师线程才能检查作业。

在此问题中,有两个线程:学生线程和老师线程,和以往的线程互斥不一样的是,线程互斥之间没有明确的的执行顺序上的要求。而线程同步,有了顺序上的要求,即有先后关系:只有学生线程完成了作业以后,老师线程才能够去运行!

2. 解决思路

在我们没有学习线程同步的方法前,除了采用信号量机制和轮询,好像还没有更好的方法来解决此问题。如果不允许使用信号量的话,用轮询该怎么做?轮询是指:老师线程可以不断的去询问学生作业到底有没有完成作业。

具体做法是采用全局变量 finished 做标记,初始化为 0,表示学生还未完成作业。学生如果完成了作业,会将 finished 变量设置为 1.

另一方面,老师线程不断的检查 finished 变量是否为 1 来判断学生完成了作业。

下面是伪代码:

void student() {
// doing homework
sleep(5);
lock(mutex);
finished = 1;
unlock(mutex);
}

void teacher() {
// 不断检查标记是否为 1
lock(mutex);
while(finished == 0) {
unlock(mutex);
sleep(1);
lock(mutex);
}
unlock(mutex);
// 执行到这里说明学生已经完成了作业

很显然,全局变量 finished 属于共享资源,需要使用互斥手段对其进行互斥访问,否则容易出现错误。

说明:在此例中不需要给 finished 变量加互斥锁也不会错,因为老师线程只做了读操作。但是,为了防止产生竞争错误,其次为了后续讨论方便,这里索性加上互斥锁。

3. 程序清单

3.1 代码

#include <stdio.h>
#include <pthread.h>

int finished = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void* do_homework(void* arg) {
// doing homework
sleep(5);
// finished
pthread_mutex_lock(&lock);
finished = 1;
pthread_mutex_unlock(&lock);
}

void* check_homework(void* arg) {
// 打电话
sleep(1);
// 电话接通
pthread_mutex_lock(&lock);
// 作业写完了吗?
printf("老师:作业写完了吗?!\n");
while(finished == 0) {
// 没写完呐!
printf("学生:没写完呐!\n");
pthread_mutex_unlock(&lock);
// 好的,你接着写
printf("老师:好的,你接着写吧!\n");
printf("-------------------------\n");
sleep(1);
pthread_mutex_lock(&lock);
printf("老师:作业写完了吗?!\n");
}
printf("学生:写完啦!\n");
pthread_mutex_unlock(&lock);
printf("老师开始检查---------------\n");
}


int main() {
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, do_homework, NULL);
pthread_create(&tid2, NULL, check_homework, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}

3.2 编译和运行

  • 编译和运行
$ gcc do_homework.c -o do_homework -lpthread
$ ./do_homework
  • 运行结果


85-线程同步_linux


图1 运行结果


3.3 结果分析

从图 1 中可以看到,老师一直问了学生 5 次,其中 4 次询问得到的答复都是没写完……

虽然本程序的结果正确,但是大家也可以看到,老师反复的询问学生,效率低下。如果学生做作业十分的慢,也不知道要做多久,老师就会一直问下去,这无疑是对老师宝贵时间的浪费(也就是浪费 cpu 资源)。

如果有一种方法,可以让学生在完成后作业后唤醒老师线程,不就好了吗?这就是所谓的好莱坞式编程:Do not call me! I will call you (别打我电话,我会通知你的).

在下一节里就会介绍这种技术——条件变量。

4. 总结

  • 理解何为线程同步
  • 知道在没有信号量和条件变量的时候,如何使用互斥来完成线程同步

练习:理解轮询机制是如何工作的,同时完成本文中的实验。