Linux条件变量&线程池详解

一、条件变量

互斥量】解决了线程间同步的问题 ,避免了多线程对同一块临界资源访问产生的冲突, 同一时刻对临界资源的访问,不论是生产者还是消费者,都需要竞争互斥锁 ,由此也带来了竞争的问题。即生产者和消费者、消费者和消费者 之间时刻都在竞争 这把锁,而临界资源是有限的,当临界资源为空 候,消费者之间的竞争便没有意义,反而降低了运行效率。

有没有什么办法可以等生产者线程生产出资源,消费者线程再去竞争锁消费呢?这就是条件变量的作用。

正如互斥量保护了【临界资源】,条件变量也保护【条件】资源,当条件不符合时即【条件】资源空缺,消费者线程休眠等待;当【条件】资源产生,消费者线程被唤醒然后去消费资源。这样便解决了线程间等待的问题,提高了运行效率。

cpp 复制代码
pthread_cond_t cond  //定义条件变量
pthread_cond_init(&cond) //动态初始化
  
pthread_cond_t cond = PTHREAD_COND_INITIALIZER //静态创建并初始化

/*消费者线程休眠等待生产者线程通知*/
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)

/*生产者线程生产完资源后发送通知*/
int pthread_cond_signal(pthread_cond_t *cond)    //唤醒一个休眠的线程
int pthread_cond_broadcast(pthread_cond_t *cond) //广播唤醒所有休眠的线程
  • 条件变量的使用要绑定互斥锁

    • 因为条件变量的使用过程中,对于生产者线程,需要产生资源当然要上锁;对于消费者线程,需要消费资源甚至还要判断资源是否为空,也要上锁
  • 【wait】函数的具体动作

    • 进入等待的线程列表中休眠并释放锁

    • 被唤醒后返回,同时上锁

wait函数的每个动作都是【原子操作】,动作连续并且不会被别的程序干扰,意味着CPU调度一定能保证动作粒一气呵成

消费线程不判断资源是否为空直接等待的话容易丢失signal信号(假设生产线程提前生产出资源也signal了)

唤醒也可以用broadcast,这种情况消费线程一定要判断资源是否为空,否则链表形式的资源容易出现【段错误】(因为多个线程去争抢资源时,总有抢不到的)

二、线程池

若干线程的集合,可用结构体来构造

必要性:当会出现大量执行时间短的任务 时,甚至这个时间比线程创建+销毁的时间还短 ,这时候再创建多个线程就不划算了,可以未雨绸缪,构建线程池,提前创建多个线程来节省开销

线程池的实现过程

1.创建任务队列、线程池的结构体、定义一个线程池

cpp 复制代码
#define pool_num (10)

typedef struct Task{
	struct Task *next;
	void *(*func)(void *);
	void *arg;

}Task;

typedef struct{
	pthread_mutex_t mutex;
	pthread_cond_t cond;
	Task *task_head;
	int busywork;
	pthread_t tid[pool_num];

}ThreadPool;

ThreadPool *pool;

2.初始化线程池、线程的工作

cpp 复制代码
void pool_init()
{
	pool = (ThreadPool *)malloc(sizeof(ThreadPool));
	pthread_mutex_init(&pool->mutex,NULL);
	pthread_cond_init(&pool->cond,NULL);
	pool->task_head = NULL;
	pool->busywork = 0;
	for(i=0;i<pool_num;i++)
	{
		pthread_create(&pool->tid[i],NULL,workthread,NULL);
	}
}

void *workthread(void *arg)
{
	while(1)
	{	
	//	usleep(10000);  //avoid other process couldn't rob the lock and several process repeatedly rob the lock
		pthread_mutex_lock(&pool->mutex);
		while(pool->task_head == NULL)
		{
			pthread_cond_wait(&pool->cond,&pool->mutex);
		}
		Task *ptask = pool->task_head;//任务取走线程池的头个线程
		pool->task_head = ptask->next;//重置线程池的头个线程为下一个
		pool->busywork--;//
		pthread_mutex_unlock(&pool->mutex);
		printf("%ld start task %d\n",pthread_self(),(int)ptask->arg);
		ptask->func(ptask->arg);
	}
}

3.添加任务

cpp 复制代码
void *realwork(void *arg)
{
	usleep(100000);
	printf("finish task %d\n",(int)arg);

}

void add_task(int arg)
{
	pthread_mutex_lock(&pool->mutex);
	while(pool->busywork >= pool_num)
	{
		pthread_mutex_unlock(&pool->mutex);
		usleep(10000);
		pthread_mutex_lock(&pool->mutex);
	}
	pthread_mutex_unlock(&pool->mutex);
    //以上为判断任务数量是否超过线程池数量,超过则不再允许添加任务
	Task *newtask;
	newtask = (Task *)malloc(sizeof(Task));
	newtask->func = realwork;
	newtask->arg = (void *)arg;
	pthread_mutex_lock(&pool->mutex);
	Task *p = pool->task_head;
	if(p == NULL)
	{
		pool->task_head = newtask;
	}else
	{
		while(p->next != NULL)
		{
			p = p->next;
		}
		p->next = newtask;
		pthread_cond_signal(&pool->cond);
		pool->busywork++;
	}
	pthread_mutex_unlock(&pool->mutex);
}	

4.销毁线程池、任务队列、互斥锁、条件变量

cpp 复制代码
void pool_destroy()
{
	Task *p;
	while(pool->task_head != NULL)
	{
		p = pool->task_head;
		pool->task_head = p->next;
		free(p);
	}
	pthread_mutex_destroy(&pool->mutex);
	pthread_cond_destroy(&pool->cond);
	free(pool);
}

示例

cpp 复制代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#define pool_num (10)
int i = 0;

typedef struct Task{
	struct Task *next;
	void *(*func)(void *);
	void *arg;

}Task;

typedef struct{
	pthread_mutex_t mutex;
	pthread_cond_t cond;
	Task *task_head;
	int busywork;
	pthread_t tid[pool_num];

}ThreadPool;

ThreadPool *pool;

void *workthread(void *arg)
{
	while(1)
	{	
	//	usleep(10000);  //avoid other process couldn't rob the lock and several process repeatedly rob the lock
		pthread_mutex_lock(&pool->mutex);
		while(pool->task_head == NULL)
		{
			pthread_cond_wait(&pool->cond,&pool->mutex);
		}
		Task *ptask = pool->task_head;//任务取走线程池的头个线程
		pool->task_head = ptask->next;//重置线程池的头个线程为下一个
		pool->busywork--;//
		pthread_mutex_unlock(&pool->mutex);
		printf("%ld start task %d\n",pthread_self(),(int)ptask->arg);
		ptask->func(ptask->arg);
	}
}

void *realwork(void *arg)
{
	usleep(100000);
	printf("finish task %d\n",(int)arg);

}

void add_task(int arg)
{
	pthread_mutex_lock(&pool->mutex);
	while(pool->busywork >= pool_num)
	{
		pthread_mutex_unlock(&pool->mutex);
		usleep(10000);
		pthread_mutex_lock(&pool->mutex);
	}
	pthread_mutex_unlock(&pool->mutex);
	Task *newtask;
	newtask = (Task *)malloc(sizeof(Task));
	newtask->func = realwork;
	newtask->arg = (void *)arg;
	pthread_mutex_lock(&pool->mutex);
	Task *p = pool->task_head;
	if(p == NULL)
	{
		pool->task_head = newtask;
	}else
	{
		while(p->next != NULL)
		{
			p = p->next;
		}
		p->next = newtask;
		pthread_cond_signal(&pool->cond);
		pool->busywork++;
	}
	pthread_mutex_unlock(&pool->mutex);
}	

void pool_init()
{
	pool = (ThreadPool *)malloc(sizeof(ThreadPool));
	pthread_mutex_init(&pool->mutex,NULL);
	pthread_cond_init(&pool->cond,NULL);
	pool->task_head = NULL;
	pool->busywork = 0;
	for(i=0;i<pool_num;i++)
	{
		pthread_create(&pool->tid[i],NULL,workthread,NULL);
	}

}

void pool_destroy()
{
	Task *p;
	while(pool->task_head != NULL)
	{
		p = pool->task_head;
		pool->task_head = p->next;
		free(p);
	}
	pthread_mutex_destroy(&pool->mutex);
	pthread_cond_destroy(&pool->cond);
	free(pool);
}

int main()
{
	printf("mypid is %d\n",getpid());
	pool_init();
	sleep(5);
	for(i=1;i<=40;i++)
	{
		add_task(i);
	}
	sleep(5);
	pool_destroy();
	while(1)
	{
		printf("now im alone\n");
		sleep(2);
	}	
	
}
相关推荐
Narutolxy17 分钟前
Ubuntu 下 Docker 企业级运维指南:核心命令与最佳实践深度解析20250309
运维·ubuntu·docker
施天助26 分钟前
开发ai模型最佳的系统是Ubuntu还是linux?
人工智能·ubuntu
cape_NO_730 分钟前
运动控制卡--概述学习
学习·自动化
酥暮沐42 分钟前
K8S 集群搭建——cri-dockerd版
linux·容器·kubernetes
美好的事情总会发生1 小时前
SDIO(Secure Digital Input Output)详解
linux·嵌入式硬件·硬件工程
飞向星河1 小时前
SV学习笔记——数组、队列
笔记·学习·c#
liuyunluoxiao2 小时前
进程(上)【Linux操作系统】
linux
胡西风_foxww2 小时前
中学学习难点管理思维魔方
学习·中学·难点
北顾南栀倾寒2 小时前
[算法笔记]cin和getline的并用、如何区分两个数据对、C++中std::tuple类
笔记·算法
琪琪花2 小时前
sshfs 将远程服务器上的文件系统挂载到本地目录
linux·运维·服务器