Linux学习_线程条件变量和状态转移图

Posted Leslie X徐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习_线程条件变量和状态转移图相关的知识,希望对你有一定的参考价值。

线程同步

一、线程转移状态图

线程状态转移图

二、条件变量

  1. 由来和概念:
  • 互斥锁的缺点是它只有两种状态:锁定和非锁定。
  • 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。
  • 条件变量内部是一个等待队列,放置等待/阻塞的线程,线程在条件变量上进行等待和通知,互斥锁用来保护等待队列(对等待队列上锁),条件变量通常和互斥锁一起使用。
  • 条件变量允许线程等待特定条件发生,当条件不满足时,线程通常先进入阻塞状态,等待条件发生变化。一旦其他的某个线程改变了条件,可唤醒一个或多个阻塞的线程。
  • 具体的判断条件还需用户给出
  • 条件变量数据类型:
    • pthread_cond_t
  1. 条件变量的创建和销毁
#include <pthread.h>

int pthread_cond_init( 
								pthread_cond_t* restrict cond,
								pthread_condattr_t* restrict attr);

int pthread_cond_destroy(pthread_cond_t* cond)
  • 返回:成功返回0,出错返回错误编号
  • 参数:
    • cond:条件变量(condition)
    • attr:条件变量属性(attribute)
  1. 条件变量等待操作
#include <pthread.h>

int pthread_cond_wait(
								pthread_cond_t* restrict cond,
								pthread_mutex_t* restrict mutex);

int pthread_cond_timewait(
								pthread_cond_t* restrict cond,
								pthread_mutex_t* restrict mutex,
								const struct timespec* restrict timeout);

struct timespec
{
	time_t tv_sec; /*seconds*/
	long tv_nsec; /*nanoseconds*/
};
  • 返回:成功返回0,出错返回错误编号
  • 参数:
    • cond: 条件变量
    • mutex: 互斥锁
  • 互斥锁mutex是对条件变量cond的保护
  • 线程由于调用wait函数阻塞,否则释放互斥锁
  1. 条件变量通知操作
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t* cond);

int pthread_cond_broadcast(pthread_cond_t* cond);
  • 返回:成功返回0,出错返回错误编号
  • 当条件满足,线程需要通知等待的线程。
  • pthread_cond_signal函数通知单个线程。
  • pthread_cond_broadcast函数通知所有线程。
  1. 条件变量单播示例
/* pthread_cal.c
 * 
 * 一个线程负责计算结果,一个线程负责获取结果
 * 
 * */

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

//这个结构体为一个共享资源,使用时需要加锁保护
typedef struct
{
	int res;
	int is_wait;	//用户给出用于判断的条件
	pthread_cond_t cond;	//条件变量
	pthread_mutex_t mutex;	//互斥锁
}Result;

//线程函数1:计算并将结果放置Result中
void* set_fn(void* arg)
{
	int i=1, sum=0;
	//计算1~100的累加,得到5050
	for(; i<=100; i++) sum += i;
	
	Result* r = (Result*)arg;
	//将计算结果放置到Result的res中
	r->res = sum;
	
	//对两个线程共享的判断条件(is_wait)进行保护(加锁)
	//两个线程对判断条件操作是互斥的
	pthread_mutex_lock(&r->mutex);
	//判断获取结果的线程是否准备好,准备好则退出循环
	while(!r->is_wait){ //若没有准备好
		//若在加锁状态下进行循环,get_fn线程无法执行将is_wait置1, 会形成死锁。所以这边进入循环先解锁
		pthread_mutex_unlock(&r->mutex);
		usleep(100);
		pthread_mutex_lock(&r->mutex);
	}
	pthread_mutex_unlock(&r->mutex);
	
	//通知唤醒等待的那个获取结果的线程
	pthread_cond_signal(&r->cond);
	
	
	return (void*)0;
}

//线程函数2:获得结果
void* get_fn(void* arg)
{
	Result *r = (Result*)arg;
	
	//对两个线程共享的判断条件(is_wait)进行保护(加锁)
	//两个线程对判断条件操作是互斥的
	pthread_mutex_lock(&r->mutex);
	
	//is_wait为1,代表获取结果的线程已经准备好
	r->is_wait = 1;
	
	//准备好则开始进行等待
	pthread_cond_wait(&r->cond, &r->mutex);
	//线程被唤醒后(注意此处的unlock是解锁wait函数内部的lock)
	pthread_mutex_unlock(&r->mutex);
	//获取计算的结果
	int res = r->res;
	printf("0x%lx get sum is %d\\n", pthread_self(), res);
	
	return (void*)0;
}

//主函数
int main(void)
{
	int err;
	pthread_t cal, get;
	
	Result r;
	r.is_wait=0; //初始化用户判断条件
	pthread_cond_init(&r.cond, NULL);
	pthread_mutex_init(&r.mutex, NULL);
	
	//启动获取结果的线程
	err=pthread_create(&get, NULL,get_fn, (void*)&r);
	if(err)perror("pthread create error");
	//启动计算结果的线程
	err=pthread_create(&cal, NULL,set_fn, (void*)&r);
	if(err)perror("pthread create error");
	
	pthread_join(cal, NULL);
	pthread_join(get, NULL);
	
	pthread_cond_destroy(&r.cond);
	pthread_mutex_destroy(&r.mutex);
	
	
	return 0;
}

输出:

0xb6de2460 get sum is 5050

  1. wait函数内部流程
pthread_cond_wait(cond, mutex)函数内部流程
{
	1)unlock(&mutex); //释放锁
	2)lock(&mutex);
	3)将线程自己插入到条件变量的等待队列中
	4)unlock(&mutex);
	5)当前等待的线程阻塞 <===等待其他线程通知唤醒(broadcast/signal)
	6)在唤醒后,lock(&mutex); //注意在wait函数后面加一个unlock函数
	7)从等待队列中删除线程自己
}
  1. 条件变量广播示例:
/* pthread_calbroadcast.c
 * 
 * 一个线程负责计算结果,多个线程负责获取结果
 * 
 * */


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

//这个结构体为一个共享资源,使用时需要加锁保护
typedef struct
{
	int res;
	int counter;	//用于统计获取结果线程的数量
	pthread_cond_t cond;	//条件变量
	pthread_mutex_t mutex;	//互斥锁
}Result;

//线程函数1:计算并将结果放置Result中
void* set_fn(void* arg)
{
	int i=1, sum=0;
	//计算1~100的累加,得到5050
	for(; i<=100; i++) sum += i;
	
	Result* r = (Result*)arg;
	//将计算结果放置到Result的res中
	r->res = sum;
	
	//对两个线程共享的判断条件(is_wait)进行保护(加锁)
	//两个线程对判断条件操作是互斥的
	pthread_mutex_lock(&r->mutex);
	//判断获取结果的线程是否达到指定的数量
	while(r->counter <2){
		pthread_mutex_unlock(&r->mutex);
		usleep(100);
		pthread_mutex_lock(&r->mutex);
	}
	pthread_mutex_unlock(&r->mutex);
	
	//通知唤醒等待的那个获取结果的线程
	pthread_cond_broadcast(&r->cond);
	
	
	return (void*)0;
}

//线程函数2:获得结果
void* get_fn(void* arg)
{
	Result *r = (Result*)arg;
	
	//对两个线程共享的判断条件(is_wait)进行保护(加锁)
	//两个线程对判断条件操作是互斥的
	pthread_mutex_lock(&r->mutex);
	r->counter++;
	
	//准备好则开始进行等待
	pthread_cond_wait(&r->cond, &r->mutex);
	//线程被唤醒后
	pthread_mutex_unlock(&r->mutex);
	//获取计算的结果
	int res = r->res;
	printf("0x%lx get sum is %d\\n", pthread_self(), res);
	
	return (void*)0;
}

//主函数
int main(void)
{
	int err;
	pthread_t cal, get1, get2;
	
	Result r;
	r.counter=0; 
	pthread_cond_init(&r.cond, NULL);
	pthread_mutex_init(&r.mutex, NULL);
	
	//启动获取结果的线程
	err=pthread_create(&get1, NULL,get_fn, (void*)&r);
	if(err)perror("pthread create error");
	//启动获取结果的线程
	err=pthread_create(&get2, NULL,get_fn, (void*)&r);
	if(err)perror("pthread create error");
	
	//启动计算结果的线程
	err=pthread_create(&cal, NULL,set_fn, (void*)&r);
	if(err)perror("pthread create error");
	
	pthread_join(cal, NULL);
	pthread_join(get1, NULL);
	pthread_join(get2, NULL);
	
	pthread_cond_destroy(&r.cond);
	pthread_mutex_destroy(&r.mutex);
	
	
	return 0;
}

以上是关于Linux学习_线程条件变量和状态转移图的主要内容,如果未能解决你的问题,请参考以下文章

四十Linux 线程——线程同步之条件变量之线程状态转换

四十Linux 线程——线程同步之条件变量

互斥锁自旋锁读写锁和条件变量

第三篇 状态图学习

Linux多线程同步之互斥量和条件变量

Linux系统编程—线程—线程条件控制实现线程的同步