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);
	}	
	
}
相关推荐
橘子真甜~12 分钟前
Linux操作系统3-文件与IO操作1(从C语言IO操作到系统调用)
linux·运维·服务器·c语言·io·文件操作·文件fd
A charmer22 分钟前
Linux 权限管理:用户分类、权限解读与常见问题剖析
linux·运维·服务器
羊村懒哥27 分钟前
linux-安全-iptables防火墙基础笔记
linux·网络·安全
孙同学_31 分钟前
【Linux篇】权限管理 - 用户与组权限详解
java·linux·服务器
skywalk816344 分钟前
完全按照手册win10里装Ubuntu 虚拟机然后编译ESP32(主要是想针对ESP32C3和S3)开发板的鸿蒙系统(失败)
ubuntu·华为·harmonyos·liteos
猫猫的小茶馆1 小时前
【Linux系统】Linux内核框架(详细版本)
linux·运维·服务器·开发语言·嵌入式软件
风行無痕1 小时前
Ubuntu 22.04 解决EasyExcel导出报错和读写问题 缺失字体
linux·运维·ubuntu
boss-dog1 小时前
Ubuntu22.04系统源码编译OpenCV 4.10.0(包含opencv_contrib)
opencv·ubuntu
码农老张Zy1 小时前
【PHP小课堂】学习PHP中的变量处理相关操作
android·开发语言·学习·php
IT古董1 小时前
【机器学习】机器学习的基本分类-监督学习-决策树-C4.5 算法
人工智能·学习·算法·决策树·机器学习·分类