基于《4.Linux C多线程的执行顺序问题》进行修改,现在要做这么一件事情,线程thread_one和线程thread_two共同处理一个全局变量i,thread_one:++i和打印i,thread_two处理的事情是:打印i。thread_one和thread_two都处理打印i的事务,所不同的是,当i为3的倍数的时候,由thread_two打印,否则由thread_one打印。
程序如下:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
static void thread_one(char* msg);
static void thread_two(char* msg);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int i = 1;
int temp;
int main(int argc, char** argv)
{
pthread_t th_one, th_two;
char * msg = "thread";
printf("thread_one starting\n");
if (pthread_create(&th_one, NULL, (void*)&thread_one, msg) != 0) {
exit(EXIT_FAILURE);
}
printf("thread_two starting\n");
if (pthread_create(&th_two, NULL, (void*)&thread_two, msg) != 0) {
exit(EXIT_FAILURE);
}
pthread_join(th_one, NULL);
pthread_join(th_two, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
printf("Main thread is going over!\n");
return 0;
}
static void thread_one(char* msg)
{
int j = 0;
while (i < 10) {
pthread_mutex_lock(&mutex);
if (i % 3 == 0) {
temp = i;
i++;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
usleep(10); // 为了让thread_two有足够执行时间,10ms
} else {
printf("I am one. loop %d\n", i);
i++;
pthread_mutex_unlock(&mutex);
}
}
printf("one is over!\n");
}
static void thread_two(char* msg)
{
int j = 0;
while (i < 10) {
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
printf("I am two. loop %d\n", temp);
}
printf("two is over!\n");
}
编译执行结果如下:
root@book-desktop:/opt/pc_test/multithreading/t# ./main
thread_one starting
thread_two starting
I am one. loop 1
I am one. loop 2
I am two. loop 3
I am one. loop 4
I am one. loop 5
I am two. loop 6
I am one. loop 7
I am one. loop 8
I am two. loop 9
two is over!
one is over!
Main thread is going over!
先来分析“pthread_cond_wait(&cond, &mutex);”
可以说是pthread_cond_wait()是条件变量机制中的最重要也是最难分析的函数。
使用条件变量机制直观上比较简单:调用pthread_cond_wait()的线程把自个儿给阻塞起来,然后等待别的线程调用pthread_cond_signal()把它唤醒。
pthread_cond_wait(&cond,&mutex)操作有两步,是原子操作:第一步是解锁,先解除之前的pthread_mutex_lock()锁定的mutex;第二步是挂起,阻塞并在等待对列里休眠,即线程thread_two挂起,直到被线程thread_one再次被唤醒,唤醒的条件是由“pthread_cond_signal(&cond);”发出的cond信号来唤醒。(这段话来自网络,总感觉有些问题,但我得明白pthread_mutex_lock()涉及两次mutex操作:上锁和解锁)
值得注意的是,pthread_cond_wait函数的使用方法如下:
pthread_mutex_lock(&mutex); // step a
pthread_cond_wait(&cond, &mutex); // step b
pthread_mutex_unlock(&mutex); // step c
step a比较容易理解了,step c如何理解呢?
这得深刻解剖pthread_cond_wait()了,先看线程thread_two是如何进入阻塞状态的:
a.解锁(解除step a锁定的mutex,如此使得与之共享锁的线程譬如thread_one能够拥有该mutex)、等待(阻塞睡眠之类的,此阻塞发生在pthread_cond_wait函数内部)。
b....等待,直到pthread_cond_signal()函数唤醒之。
c.此时被唤醒的线程上下文仍然在pthread_cond_wait函数内部,此时加锁,使得mutex被线程thread_two重新拥有,然后处理返回值之类的。
所以上面的step a、b、c可以解剖为如下这般:
a------- pthread_mutex_lock(&mutex);
b1------- pthread_mutex_unlock(&mutex);
睡眠............唤醒
b2------- pthread_mutex_lock(&mutex);
c------- pthread_mutex_unlock(&mutex);
理解“pthread_cond_wait(&cond, &mutex);”有两个关键:
1.pthread_cond_wait函数涉及两次mutex操作;
2.pthread_cond_wait函数并不是1次原子操作,线程阻塞于此函数内部,同样线程也是在此函数内部被唤醒。
接着来分析“pthread_cond_signal(&cond);”
关于pthread_cond_signal(),网文曰:
“pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行。如果没有线程处在阻塞等待状态,pthread_cond_signal()也会成功返回。”
“使用pthread_cond_signal函数不会有‘惊群现象’产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal()最多发信号一次。”
需要注意的是pthread_cond_signal函数不涉及mutex的操作,所以在thread_one里调用pthread_cond_signal函数,thread_two只能说被唤醒,但此时mutex还未被释放掉,也即thread_two的程序还不能被执行,直到thread_one释放掉mutex为止。
上文已经提到,对于pthread_cond_wait()的使用方法,大概如下:
pthread_mutex_lock(&mutex); // step a
pthread_cond_wait(&cond, &mutex); // step b
pthread_mutex_unlock(&mutex); // step c
事实上,若需要在thread_two中处理共同资源,最好如下般这样:
pthread_mutex_lock(&mutex); // step a
pthread_cond_wait(&cond, &mutex); // step b
...处理共享数据 // step c
pthread_mutex_unlock(&mutex); // step d
同样,对于pthread_cond_signal()的使用方法,也可以如下般这样:
pthread_mutex_lock(&mutex); // step a
pthread_cond_signal(&cond, &mutex); // step b
...处理共享数据 // step c
pthread_mutex_unlock(&mutex); // step d
本文程序并不是一个多么好的条件变量的应用实例,但通过它足以体会条件变量的使用方法~