线程池的简单实例

线程池初步(非标准写法)

在上篇文章《线程与竞争》的例一中,我用了200个线程来判断质数,但是实际上需要这么多线程吗?
其实可以用分块、交叉分配、池类写法来实现若干个线程判断质数
我们知道,线程和线程之间是共用同一个进程空间的,所以线程之间通信只要设置一个全局变量就可以了,因为线程就是跑着的函数
但是呢,如果你用进程那一套东西,管道,共享内存这些的,相对于全局变量都是绕远路,对吧
先理解一个场景:
在一个公司里,老板有一个项目,可以安排给手下若干个员工,并且奖励非常的丰厚
员工一听,这么好的项目,大家都想要,所以大家都从摆烂的状态里醒过来,来抢占这个任务
那么,我们的实现方法就是,mian线程给任务,交由线程来抢占,然后某个线程抢到以后,就判断是不是质数
那么有几个问题,假如A线程抢到了30000000这个数
一、怎么告诉其他虎视眈眈的线程,这个数字已经被A线程抢占了
二、怎么告诉mian线程,30000000这个任务已经被取到,让mian线程继续下放任务
很简单,设置一个变量number,mian线程往这个变量上下发任务,比如放置30000000,然后让线程去抢夺,如果A线程抢到以后,就迅速保存这个数值
并且把number置0,当其他线程看到是0的时候就知道他们已经来晚一步了,任务已经被抢走了,当mian线程看到0的时候就知道任务已经被领走
就可以继续下放任务
那么,最后一个问题,当mian线程放完任务的时候,那个变量number是0,而线程们又眼巴巴的等着,这个时候怎么办呢?
所以还要有一个退出环节
下面给出规定:
num > 0:表示任务
num = 0:表示无任务
num = -1:表示提醒线程退出,mian线程回收
请看程序

判断质数

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

#define LEFT 30000000
#define RIGHT 30000200
#define THRNUM 4		//假定有4个线程在线程池里

static int num = 0;		//上游mian线程和下游被创建的线程的窗口,通过num的值来执行对应的业务
static pthread_mutex_t mut_num = PTHREAD_MUTEX_INITIALIZER;		//创建互斥量并初始化

static void* thr_prime(void* p);		//声明函数

int main()
{
	int i, err;
	pthread_t tid[THRNUM];		//创建4个线程表示

	for (i = 0; i < THRNUM; i ++ )
	{
		err = pthread_create(tid + i, NULL, thr_prime, (void*)i);	 //创建线程,参数:(要回填的线程标识, 默认属性, 线程的执行函数, 传给线程执行函数的参数)
		if (err)			//检查是否创建线程失败
		{
			fprintf(stderr, "pthread_create():%s\n", strerror(err));
			exit(1);
		}
	}

	for (i = LEFT; i <= RIGHT; i++)		//下发任务
	{
		pthread_mutex_lock(&mut_num);	//锁上,如果已经被锁上了,那么就阻塞等待
										//锁上的原因:多个线程(包括mian线程)都访问同一个变量地址,为了避免竞争
		while (num != 0)				//mian线程能执行到这里,说明num变量是mian线程独占的
		{
			pthread_mutex_unlock(&mut_num);		//当num != 0的时候标识任务还没有被下游线程领走,那么就解锁给下游线程一点机会
			sched_yield();
			pthread_mutex_lock(&mut_num);		//然后继续锁上

		}
		num = i;			//下发任务
		pthread_mutex_unlock(&mut_num);			//mian线程到这里已经完成了任务的下发, 或者说后续对num变量的访问只是读不是写,所以这个时候可以解锁了

	}

	pthread_mutex_lock(&mut_num);		//mian线程到这里已经完成了所有的任务,所以要把num改成-1, 更改的前提是num == 0也就是任务真的发好了
										//既然要改num的值,那么就要确保num是mian线程独占的,所以要锁上
	while (num != 0)
	{
		pthread_mutex_unlock(&mut_num);		//如果不为0表示还没有发好,那就松开一会,然后再锁上
		sched_yield();
		pthread_mutex_lock(&mut_num);
	}
	num = -1;			//真的发好了
	pthread_mutex_unlock(&mut_num);		//已经改好了num,此后mian线程对num只是读,并不需要写,所以解锁

	for (i = 0; i < THRNUM; i++) pthread_join(tid[i], NULL);		//收尸

	pthread_mutex_destroy(&mut_num);		//释放互斥量
	exit(0);				//结束程序
}

static void* thr_prime(void* p)			//切记,线程只是一个运行的函数,和其他线程都是兄弟关系,不是父子,没有主次之分
{
	int i, mark;
	while (1)
	{
		pthread_mutex_lock(&mut_num);		//下游线程抢到了任务,那么要确保是独占的,所以要锁上
		while (num == 0)					//num != 0 才代表有任务,如果为0那么就松开一会,让mian线程下放任务
		{
			pthread_mutex_unlock(&mut_num);
			sched_yield();
			pthread_mutex_lock(&mut_num);
		}
		
		if (num == -1)						//如果任务都发好了,那么就可以结束了
		{
			pthread_mutex_unlock(&mut_num);		//避免死锁!这点程序后讲吗这个非常重要
			break;
		}
		i = num;							//领取任务
		num = 0;							//告诉mian线程和其他线程,任务已经被领走了
		pthread_mutex_unlock(&mut_num);		//下游线程领走任务以后,对num就只是读不需要写,所以解锁
		
		//判断质数,时间复杂度根号i
		mark = 1;
		for (int j = 2; j <= i / 2; j ++ )
			if (!(i % j))
			{
				mark = 0;
				break;
			}
		if (mark) printf("[%d]: %d is a primer\n", (int)p, i);	//p是传过来的参数,取值范围是0, 1, 2, 3,所以可以人为的当作是线程的标识
	}
	
	pthread_exit(NULL);			//结束
}
这里着重讲两个问题,其他的问题在注释里讲的比较清楚,至少我是这么觉得的
	问题一:那三行是什么意思
		pthread_mutex_unlock(&mut_num);
		sched_yield();
		pthread_mutex_lock(&mut_num);
			
	问题二:
	if (num == -1)						//如果任务都发好了,那么就可以结束了
		{
			pthread_mutex_unlock(&mut_num);		//避免死锁!这点程序后讲吗这个非常重要
			break;
		}
	为什么break前要解锁,不是break完以后就可以直接pthread_exit结束吗
	
OK, 一个一个来
问题一:以mian线程为例,当mian线程下发任务的时候,他期待的是num = 0的状态,这样他就可以下发任务了
但是呢,在此之前,肯定要判断num的状态呀,如果num不是0的话,那么就松手(此前已经锁住了),然后迅速再锁上
那么,问题一还有一个子问题,为什么是while不是if ?很简单,万一松手的时候,别的线程没抢到怎么办
啊对!问题来了,其他线程没抢到怎么办?很简单啊,松手的时间长一点呗,sleep一会呗
我们来详细剖析一下sleep,当一个进程里出现sleep的时候,就会引发进程状态的颠簸,从runing态变成可中断的睡眠态,当sleep过后,又变成runing态
这个进程状态的变化是没有必要的,所以用sched_yield函数

最后,提一下sched_yield()函数, sched_yield()这个函数可以使用另一个级别等于或高于当前线程的线程先运行。
如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。
可以理解为非常非常短的sleep函数,并且并不会引起进程状态的颠簸
	
	
问题二:
仔细看程序,在判断num是否为-1的上面有一个while循环,可以看到,在if执行的时候,这个时候是这个当前这个线程独占num,
所以,如果当前这个线程没有解锁,直接跳出,然后结束线程,那么其他的线程只能干巴巴的等,并且这个线程还没人收尸

切记,从锁上到解锁都是临界区,在临界区里,我们要留心任何一个跳转指令,比如说:break,continue,goto这些
任何一个跳转指令,如果是跳转到临界区内,那无所谓,反正还在临界区内,还没有解锁,但是!
如果你跳转到了临界区外, 一定要先解锁,然后才能跳转

那么分析到这里,整个程序,大家看懂了吗?

其实还有一个很细节的地方
请看mian线程的最后
num = -1
这里是为了设置num = -1, 告诉其他线程,该结束了
显然,这个是某个线程(mian线程)对num的访问,所以前后都应该加锁,确保是mian线程独占,OK,没问题
那么问什么要判断num的状态呢?因为,当mian线程下发最后任务后,此时如果又被mian线程抢到了执行机会,那么他肯定直接把num赋值为1

这是不是意味着我们的程序有一个逻辑bug,最后一个任务,有可能会不执行,那么当我们做大型项目的时候,面对百万并发的时候,后果将把不堪设想

所以需要判断num的状态,使用while来判断(为什么使用while上面讲了)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值