线程同步--条件变量

121 阅读6分钟

最近在看多线程相关问题的时候,发现之前对条件变量的理解不够深入,再次回去看了下书,总结记录。

多线程循环打印的问题,网上搜了很多都是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()所处理的情况是:处于等待状态的所有线程执行的任务不同(即各线程关联于条件变量的判定条件不同)

  • 条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制。发送信号时,若无任何线程在等待该条件变量,这个信号也就会不了了之

条件变量和互斥量:

  1. 线程在准备检查共享变量状态时锁定互斥量
  2. 检查共享变量的状态
  3. 如果共享变量未处于预期状态,线程应在等待条件变量并进入修面前解锁互斥量(以便其他线程能访问该共享变量)
  4. 当线程因为条件变量的通知而被再度唤醒时,必须对互斥量再次加锁,因为在典型情况下,线程会立即访问共享变量

示例

两个线程循环打印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…