最近在看多线程相关问题的时候,发现之前对条件变量的理解不够深入,再次回去看了下书,总结记录。
多线程循环打印的问题,网上搜了很多都是jave C++的帖子,记录下C相关的
问题:由两个线程循环打印ab-->三个线程循环打印abc
先复习一下多线程中的条件变量:
条件变量
条件变量允许一个线程就某个共享变量(或其他共享资源)的状态变化通知其他线程,并让其他线程等待(阻塞于)这一通知(pthread_cont_wait)
条件变量允许一个线程休眠(等待)直至接获另一线程的通知(收到信号)去执行某些操作(pthread_cond_signal/pthread_cond_broadcast)
条件变量总是结合互斥量使用。条件变量就共享变量的状态改变发出通知,而互斥量则提供对该共享变量的访问的互斥.(所以在使用条件变量时,先获取锁,再判断条件)
通知和等待条件变量
条件变量的主要操作是发送信号和等待。发送信号操作即通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变。
等待操作是指在收到一个同之前一直处于阻塞状态
pthread_cond_signal(pthread_cond *cond)
pthread_cond_broadcast(pthread_cond_t *cond)
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_broadcast和 pthread_cond_signal:对于阻塞于pthread_cond_wait()的多个线程的处理方式不同。
pthread_cond_signal()只保证唤醒至少一条遭到阻塞的线程,pthread_cond_broadcast()会唤醒所有遭到阻塞的线程
-
使用pthread_cond_broadcast()总能产生正确的结果(因为所有线程应都能处理多余和虚假的唤醒动作),但函数pthread_cond_signal()会更高效。
-
不过,只有当仅需唤醒一条(且无论是哪一条)等待线程来处理共享变量的状态变化时,才应使用pthread_cond_signal().应用这种方式的典型情况是,所有等待线程都在执行完全相同的任务。
-
相比之下,pthread_cond_broadcast()所处理的情况是:处于等待状态的所有线程执行的任务不同(即各线程关联于条件变量的判定条件不同)
-
条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制。发送信号时,若无任何线程在等待该条件变量,这个信号也就会不了了之
条件变量和互斥量:
- 线程在准备检查共享变量状态时锁定互斥量
- 检查共享变量的状态
- 如果共享变量未处于预期状态,线程应在等待条件变量并进入修面前解锁互斥量(以便其他线程能访问该共享变量)
- 当线程因为条件变量的通知而被再度唤醒时,必须对互斥量再次加锁,因为在典型情况下,线程会立即访问共享变量
示例
两个线程循环打印ab
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int signal=0;
void func1(void){
for(int i=0; i<100; i++){
pthread_mutex_lock(&mutex);
while(signal==1)
pthread_cond_wait(&cond, &mutex);
printf("a");
signal = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
void func2(void){
for(int i=0;i<100; i++){
pthread_mutex_lock(&mutex);
while(signal==0)
pthread_cond_wait(&cond, &mutex);
printf("b");
signal = 0;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
}
int main(){
pthread_t pid1, pid2;
if(pthread_create(&pid1, NULL, &func1, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_create(&pid2, NULL, &func2, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_join(pid1, NULL)==-1) return 0;
if(pthread_join(pid2, NULL)==-1) return 0;
return 0;
}
多线程交替打印abc
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int signal=0;
void func1(void){
int i;
for(i=0; i<100; i++){
pthread_mutex_lock(&mutex);
while(signal!=0)
pthread_cond_wait(&cond, &mutex);
printf("a");
signal = 1;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
}
void func2(void){
int i;
for(i=0;i<100; i++){
pthread_mutex_lock(&mutex);
while(signal!=1)
pthread_cond_wait(&cond, &mutex);
printf("b");
signal = 2;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
}
void func3(void){
int i;
for(i=0;i<100; i++){
pthread_mutex_lock(&mutex);
while(signal!=2)
pthread_cond_wait(&cond, &mutex);
printf("c");
signal = 0;
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
}
}
int main(){
pthread_t pid1, pid2, pid3;
if(pthread_create(&pid1, NULL, &func1, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_create(&pid2, NULL, &func2, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_create(&pid3, NULL, &func3, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_join(pid1, NULL)==-1) return 0;
if(pthread_join(pid2, NULL)==-1) return 0;
if(pthread_join(pid3, NULL)==-1) return 0;
return 0;
}
会有死锁,分析:按照上面的概念,pthread_cond_signal()只会唤醒一个线程,所以会有可能func1唤醒func3,但是func3等待的条件是signal==2,所以会无限等待下去
改进1:使用pthread_cond_broadcast代替 pthread_cond_signal
改进2:不同的线程是不同的任务,所以使用不同的条件变量,这样 pthread_cond_signal()可以指定唤醒的条件变量(参考了这里面的实现:www.tutorialspoint.com/print-1-2-3…)
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond3 = PTHREAD_COND_INITIALIZER;
int signal=0;
void func1(void){
int i;
for(i=0; i<100; i++){
pthread_mutex_lock(&mutex);
while(signal!=0)
pthread_cond_wait(&cond1, &mutex);
printf("a");
signal = 1;
pthread_cond_signal(&cond2);
pthread_mutex_unlock(&mutex);
}
}
void func2(void){
int i;
for(i=0;i<100; i++){
pthread_mutex_lock(&mutex);
while(signal!=1)
pthread_cond_wait(&cond2, &mutex);
printf("b");
signal = 2;
pthread_cond_signal(&cond3);
pthread_mutex_unlock(&mutex);
}
}
void func3(void){
int i;
for(i=0;i<100; i++){
pthread_mutex_lock(&mutex);
while(signal!=2)
pthread_cond_wait(&cond3, &mutex);
printf("c");
signal = 0;
pthread_cond_signal(&cond1);
pthread_mutex_unlock(&mutex);
}
}
int main(){
pthread_t pid1, pid2, pid3;
if(pthread_create(&pid1, NULL, &func1, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_create(&pid2, NULL, &func2, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_create(&pid3, NULL, &func3, NULL)==-1){
printf("create err\n");
return 0;
}
if(pthread_join(pid1, NULL)==-1) return 0;
if(pthread_join(pid2, NULL)==-1) return 0;
if(pthread_join(pid3, NULL)==-1) return 0;
return 0;
}
扩展-setjmp/longjmp
最近刚好在看协程相关的, 所以试着用C的longjmp实现了一下(准备地说setjmp/longjmp不算协程,setjmp不能处理一些栈保存的问题,具体的还没梳理出来。但是协程的大概思想是差不多这样的:主函数控制程序执行流,在程序执行过程中可以暂停执行,并且保存上下文状态,跳转到指定地方执行)
#include <stdio.h>
#include <setjmp.h>
jmp_buf buf1;
jmp_buf buf2;
jmp_buf buf3;
jmp_buf bufmain, bufmain2, bufmain3;
int cnt=0;
void func1(void){
if(setjmp(buf1)==0)
longjmp(bufmain,1);
printf("a");
longjmp(buf2,1);
}
void func2(void){
if(setjmp(buf2)==0)
longjmp(bufmain2,1);
printf("b");
longjmp(buf3, 1);
}
void func3(void){
if(setjmp(buf3)==0)
longjmp(bufmain3,1);
printf("c");
longjmp(bufmain, 1);
}
int main(){
int i=0;
if(setjmp(bufmain)==0)
func1();
if(setjmp(bufmain2)==0)
func2();
if(setjmp(bufmain3)==0)
func3();
cnt++;
if(cnt==100) return 0;
longjmp(buf1,1);
return 0;
}
关于setjmp/longjmp可以参考: www.51cto.com/article/643…