#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
pthread_cond_t c = PTHREAD_COND_INITIALIZER;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
int done = 0;
void *child(void *arg) {
sleep(1);
printf("\n--- running child thread ---\n\n");
sleep(1);
pthread_mutex_lock(&m);
done = 1;
pthread_cond_signal(&c);
pthread_mutex_unlock(&m);
return NULL;
int main() {
pthread_t p;
printf("main thread start ...\n");
assert(pthread_create(&p, NULL, child, NULL) == 0);
pthread_mutex_lock(&m);
while (done == 0) {
pthread_cond_wait(&c, &m);
pthread_mutex_unlock(&m);
printf("main thread end ...\n");
加锁的地方有两个, 分别是pthread_cond_wait
和pthread_cond_signal
函数的前后.
锁的意义在于保护, 在临界区加锁, 这样其他线程在执行临界区, 检测到锁被锁住了, 就不能访问执行这段临界区了, 直到锁被释放.
一个最简单的并发问题是如果创建2个线程同时大量操作同一个数自增(比如自增10000000次, 7个0), 那么执行完之后, 这个数往往并没有自增20000000次, 而是更少. 这就是因为在这段临界区中, 由于线程调度的存在, 且自增操作不是原子操作, 而导致某些时刻两个线程"同时"自增而这个数只增加了1. 而在临界区加锁就可以很好地避免这个问题, 执行临界区代码也就是自增时, 它被看作是原子的, 因而每执行一次就增一次.
当然, 加锁保证了正确性, 但会影响性能(甚至比一个线程自增20000000还要慢). 这是因为在加了锁的临界区, 另一个线程运行到这里而检测到这个临界区被锁住, 他就会退出, 继续进入就绪状态等待被调度. 在这种高并发的前提下, 这样的冲突是会大量存在的, 而线程的上下文切换会消耗大量的时间, 因而反而比只有一个线程运行要更慢了.
好了, 说了这么多关于锁的题外话, 回到当前的问题. 锁在这个条件变量这里起到了什么做用?
我们反向地思考. 思考如果没有锁, 会有哪些特殊情况发生:
假设线程执行到了while (done == 0)
, 此时新线程没有执行, 因而条件判断为真. 接下来应该执行pthread_cond_wait(&c, &m);
. 然而, 此时发生时钟中断, 内核调度程序, 新线程抢占执行, 并且执行完了, done 变成1了, 而且还给条件变量发送了signal了.
然后回到主线程主线程继续执行pthread_cond_wait(&c, &m);
, 然后凉了. 因为主线程会挂起, 等待信号被发送给条件变量从而将它唤醒. 但是, 新线程已经执行完了, 它无法被唤醒了.
这之中的主要矛盾就是, while (done == 0)
和pthread_cond_wait(&c, &m);
被分开了. 他们本来应该是在一起的. pthread_cond_wait(&c, &m);
是在done == 0
为真的条件下执行的, 但当新线程执行完毕, done == 0
不为真了, 他就不应该执行了.
所以这里是临界区, 它缺把锁. 将while (done == 0) { pthread_cond_wait(&c, &m); }
锁住了, 在执行pthread_cond_wait(&c, &m);
之前CPU去执行新线程了, 但他检测到锁m被锁住了, 就放弃执行, 从而不会提前发送signal.
所以, 在并发情况下, 互斥锁在这里尤其重要.
一个线程的结束, 有两种方式, 一种是正常执行完毕, 函数return之后线程结束.
第二种是调用pthread_exit
函数, 它的原型为
简单来说我们可以在线程中使用这样的方法pthread_exit(NULL);
去终止线程.
下面是一个小demo
* 基本的多线程程序,有join和无join
#include <pthread.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
void *child(void *arg) {
sleep(1);
printf("\n--- running child thread ---\n\n");
pthread_exit(NULL);
printf("do I print?\n");
sleep(1);
int main() {
pthread_t p;
printf("main thread start ...\n");
assert(pthread_create(&p, NULL, &child, NULL) == 0);
pthread_join(p, NULL);
printf("main thread end ...\n");
do I print?
这句话是不会打印出来的, 因为在此之前, 这个线程已经提前终止了.
参考资料: