linux线程池设计讲解

试着写了一下线程池的实现,任务队列采用链表实现,在运行过程中,出现了死锁现象,于是就仔细添加打印信息,在枷锁的上下文,最终定位了问题。主要是多个if语句的时候会出现忘记解锁的问题。

                                linux高级编程综合应用:线程池  

原文地址:http://blog.163.com/jimking_2010/blog/static/171601535201311113245917/

线程池:

在处理较复杂的任务的时候,可以采用多线程的方式。一般都是有一个任务,交给一个线程去处理。但是线程的创建需要一定的时间,且线程也不能无限制创建,那样CPU执行效率会更低。因此出现了线程池的概念。线程池最基本的思想就是在初始化程序时,创建出多个线程,让这些线程均处于阻塞状态。当有任务需要处理时,由主线程唤醒一个线程接收这个任务并处理,处理完后,该线程继续处于阻塞状态。如果任务个数超过线程个数,那么任务将被暂存在任务队列中,由主线程对任务的处理时间做统一的调度。如果执行过程中,一个线程退出,那么主线程要动态创建一个线程,即线程池中线程个数固定。
关于线程池,推荐参考阅读下面的链接:

线程池的实现思路:
1、线程池,顾名思义,就是要创建很多线程。创建线程的函数pthread_creat()应该是最容易被想到的。有线程创建就要有线程退出pthread_exit(),在线程退出前,如果线程没有设置pthread_detach()属性,那么显然要回收线程资源pthread_join()。当然咯,可能要获取线程的ID值pthread_self()。
2、第一步创建了线程,刚开始线程是不做事情的,初始化好了,就等待。等待当然不会是while(1)这种函数,因为那样太消耗CPU资源。容易想到的等待自然是使用条件变量的等待pthread_cond_wait(),这个函数干两件事情,第一件是对解除与形参mutex对应的互斥锁,然后是重新加锁,为的是在线程将任务放入任务队列的一个缓冲。任务放入完成后,再加锁,这样不会影响其他任务获取加锁的权利。因此,在调用该函数之前,会自然会想到加互斥锁。初始化互斥锁函数pthread_mutex_init(),反初始化互斥锁函数pthread_mutex_destroy(),加锁函数pthread_mutex_lock(),解锁函数pthread_mutex_unlock(),稍微再细化一点,可能会用到尝试解锁pthread_mutex_trylock()。
3、当然为了让线程的功能,锁的功能,条件变量的功能更加强大,都会有可设置其属性的函数与之对应。
1】线程的分离属性:线程属性初始化函数pthread_attr_init()、属性反初始化函数pthread_attr_destroy()。这是最常用的,当然可能会设置线程的分离属性,即第1点提到的pthread_detach()。那么就需要设置线程的分离属性 pthread_attr_setdetachstate()、pthread_attr_getdetachstate()。线程的分离属性其实是设置线程结束后由谁来回收的问题,是由主线程调用pthread_join()函数回收,还是由系统自己回收。默认情况下,当然是要自己回收的。
2】线程缓冲区设置:缓冲区的存在,让程序员操作线程指向的内存时,偶尔的越界不会造成很大的问题。C语言本身没有越界检查,不过一定要的话,还是可以有的(mudflap工具 http://blog.chinaunix.net/uid-502064-id-2939701.html)。缓冲区设置函数pthread_attr_setguardsize()、pthread_attr_getguardsize()。要注意的是,一旦设置了,就得设置对,因为设置完后,系统都不给你栈的缓冲区了。
3】线程栈的设置:每个线程都有自己的栈,栈的属性在应用层来说,也就两个,一个是从什么地方开始,第二个就是多大的内存,就好比是圈地。栈设置函数pthread_attr_setstack()、pthread_attr_getstack()。当然设置完后,可能要对栈进行微调,总不可能丢弃原来的数据重新建个栈,或者重新建个栈把数据复制过去吧,虽然可行,但那样显然太麻烦。基址微调函数:pthread_attr_setstackaddr()、pthread_attr_getstackaddr()。栈的大小微调函数pthread_attr_setstacksize()、pthread_attr_getstacksize()。
4】对于互斥量的属性,分进程的共享属性和类型属性。自然多线程不会涉及到进程通信的内存映射问题,因此其共享属性自然是默认的PTHREAD_PROCESS_PRIVATE,不同线程访问同一共享空间(差不多全局变量那种了)。互斥量的类型属性,主要分4个(我是cent OS系统):PTHREAD_MUTEX_TIMED_NP(不能称重复加锁,可以由别的线程来解锁)、PTHREAD_RECURSIVE_NP(同一线程可以对其重复加很多次锁,当然解锁也要相同的次数)、PTHREAD_MUTEX_ERRORCHECK_NP(自己的锁只能自己解)、PTHREAD_MUTEX_APAPTIVE_NP(未查资料,用处也不大)。
5】条件变量的属性。pthread_condattr_init()、pthread_condattr_destroy()。另外还有查询条件变量的进程共享属性的函数pthread_ condattr_getpshared()、pthread_ condattr_setpshared(),因为多线程的关系,自然不必过多关心。
4、实现了上面三步,一个线程池的框架就初步搭起来了。当然没法用,因为真正干事情的线程全部在等待中,注意不应该是超时的等待pthread_cond_timewait()。要使处于阻塞状态的线程干事情,得用信号去唤醒它pthread_cond_signal(),“打鸟”的一个函数,开一枪,总会把这只鸟吵醒,但具体是那一只,看那只最先在那排队了(上面已经说了pthread_cond_wait()函数的等待队列问题)。当然也可以想到“打鸟惊群”的函数pthread_cond_broadcast(),打一枪,无论打没打着,一群鸟都飞走了。
5、有了上面的基础,接下来就重点关注任务部分了。当然线程数量有限,上面已经说了,是固定的数目。因此任务大于线程数时,排队是难免的了。因此创建了一个任务链表,任务链表每一个节点代表一个任务,要完整的表述一个任务,自然最关心的自然是如果处理它。因此任务链表的节点最简单的模型就是一个处理任务的回掉函数void* (*callback_function)(void *arg)。指向函数的指针,参数是个指针,返回值也是个指针。具体的函数和参数则需要另外写函数定义。没次调用完线程处理完这个任务,就需要把它从任务队列中删除。进入任务队列的任务数也不能无限多,因此也设为一个比线程数稍微大个几个的一个固定值。
6、线程动态创建:一个线程退出后(在任务执行失败时,就有可能会退出),主线程要能检测到,然后动态创建一个新的线程,以维持线程池中线程总数不变。可以通过pthread_join()阻塞等待回收子线程资源,但是这就意味着主线程在阻塞状态下干不了其他工作,因此考虑使用线程信号,在子线程结束时,给主线程用pthread_kill()发送一个SIGUSR1信号,在主线程接收到此信号时,通过调用注册函数signal()或sigaction()函数注册的函数创建一个新的线程。
7、出错的任务的处理:写入错误日志。用可变参方法。
8、还是没法运行。没有任务可处理,如何运行?模块至此已经封装完毕,可由任意多任务的系统调用。实现通用。

涉及到的结构体可参考定义如下:
typedef struct job 
{
 void* (*callback_function)(void *); 
 void *arg; 
 struct job* next; 
} S_JOB;
 /*****************************队列状态********************************/ 
typedef struct queue_attribute //队列属性 
{
 S_JOB* head; //1.指向job 队列中的头节点 
 S_JOB* tail; //2.指向job 队列中的新添加的任务的下标(队列头) 
 int max_num; //3.job队列中允许的最大个数的任务
 int cur_num; //4.job 队列当前的工作数 
 pthread_cond_t empty; //5.job队列为空 通知destroy函数可以释放释放资源 
pthread_cond_t not_empty; //6.用于通知 工作线程,队列不为空,可以来取任务。
 pthread_cond_t not_full; //7.用于唤醒addjob()函数可以添加任务进队列 
 pthread_cond_t full; //8.用于通知增加一个线程池 
 int queue_close; //9.队列关闭标志 
} S_QUEUE_ATTR; 
/******************************线程池属性********************************/ 
typedef struct threadpool 
{
 pthread_mutex_t pool_mutex; //1.防止多线程同时更改此属性结构体变量 
 int thread_num; //2.线程的数量 
 pthread_t *pthreads; //3.线程池中所有线程的
pthread_t int pool_close; //4.线程关闭标志 
 S_QUEUE_ATTR queue_attr;//5.队列属性成员
 } S_THREAD;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值