线程池的分析与实现

在需要频繁开线程时,创建和销毁线程会话费大量时间,为了提高效率,我们可以在任务开始前,先创建一定数量的线程。这样在接收到任务时,就可以直接使用线程池中处于wait状态的线程,在任务结束后线程回到wait状态,等待新任务的到来,这就避免了线程的创建与销毁,从而提高程序执行效率。

所需数据

  • 需要存储有多少线程( int thread_number )
  • 需要开辟对应的数组,存储线程号( pthread_t *threads )
  • 需要一个任务队列来存储未执行的任务,便于线程竞争任务并执行( task_queue )
  • 需要一个flag来标记线程池是否结束,该标记可以在线程池结束后唤醒所有处于等待线程的线程,让它们可以正常退出(其中,所有线程处于脱离(detach)状态)
  • 互斥锁与条件变量,用于避免在获取与添加任务时发生错误(同步与互斥)

运行流程

  • 执行ThreadPool的构造函数,初始化有关数据,进行线程的创建,并将线程进行脱离(使线程在运行完后可以自动回收)
  • 创建的线程会去执行工作线程,如果任务队列为空,则一直while循环等待,直到被唤醒(signal)去竞争任务。若竞争到任务,则去执行,执行完后操作与前面相同;若没有竞争到任务,则回到wait状态
  • 有新任务到来,接收到信号的程序会给线程池的任务队列添加信息,线程池便会唤醒处于wait的线程执行任务,或直接被刚执行完任务的线程继续执行(由于是任务队列,所以只有处于队首的任务会被执行)

代码实现(c++,后面会有对代码的分析)

源代码请看我的Github

封装线程池的类
// threadPool.h#ifndef _THREADPOOL_H_
#define _THREADPOOL_H_#include "locker.h"  // 该头文件见文章最后 或 Github
#include <queue>
using namespace std;template< typename T >
class ThreadPool {
private:int thread_number;  // 线程池的线程数pthread_t *threads;  // 线程数组queue<T *> task_queue;  // 任务队列MutexLocker queue_mutex_locker;  // 任务队列的互斥锁Cond queue_cond_locker;  // 任务队列的条件变量bool m_stop;  // 是否结束线程
public:ThreadPool( int thread_num = 20 );~ThreadPool();bool append( T *task );  // 向任务队列添加任务
private:static void *worker( void * );  // 工作线程void run();  // 线程池中线程开始运行的函数T *getTask();  // 从任务队列中获取队首的任务
};template< typename T >
ThreadPool<T>::ThreadPool( int thread_num ) :thread_number(thread_num), threads(NULL), m_stop(false) {if( thread_number < 0 ) {cout << "thread_number < 0\n";throw exception();}// 创建数组存放线程号threads = new pthread_t[ thread_number ];if( !threads ) {cout << "threads is NULL\n";throw exception();}// 创建规定数量的线程for( int i = 0; i < thread_number; i++ ) {// 由于pthread_create第三个参数必须指向静态函数,要使用类成员函数和变量,只能通过:// 1) 类的静态对象// 2) 将类的对象作为参数传给静态函数// 这里通过第二种方法实现if( pthread_create( &threads[i], NULL, worker, this ) ) {  // 成功返回0delete[] threads;  // 创建失败则释放所有已分配的空间cout << "pthread_create error\n";throw exception();}// 将线程进行脱离,线程运行完后自动回收,避免使用主线程进行join等待其结束if( pthread_detach( threads[i] ) ) {delete[] threads;cout << "pthread_detach error\n";throw exception();}}
}// 析构函数中,将m_stop置true,此时将阻塞中的所有线程唤醒
// 由于 !m_stop 为false,线程会退出循环,线程结束被回收( 详见函数run() )
// 若不唤醒线程,则在程序退出后,线程非正常结束,同时会导致
template< typename T >
ThreadPool<T>::~ThreadPool() {delete[] threads;m_stop = true;queue_cond_locker.broadcast();
}/* 添加任务时需要先上锁,并判断队列是否为空 */
template< typename T >
bool ThreadPool<T>::append( T *task ) {queue_mutex_locker.mutex_lock();bool need_signal = task_queue.empty();  // 记录添加任务之前队列是否为空task_queue.push( task );queue_mutex_locker.mutex_unlock();// 如果添加任务之前队列为空,即所有线程都在wait,所以需要唤醒某个线程if( need_signal ) {queue_cond_locker.signal();}return true;
}// 线程函数,调用run()来使线程开始工作
template< typename T >
void * ThreadPool<T>::worker( void *arg ) {ThreadPool *pool = ( ThreadPool * )arg;pool->run();return pool;
}// 获取处于队首的任务,获取时需要加锁,避免发生错误
// 若队列为空,则返回NULL,该线程成为等待状态(详见函数run())
template< typename T >
T* ThreadPool<T>::getTask() {T *task = NULL;queue_mutex_locker.mutex_lock();if( !task_queue.empty() ) {task = task_queue.front();task_queue.pop();}queue_mutex_locker.mutex_unlock();return task;
}template< typename T >
void ThreadPool<T>::run() {while( !m_stop ) {  // 当线程池没有结束时,线程循环获取任务进行执行T *task = getTask();if( !task ) {queue_cond_locker.wait();  // 队列为空,线程开始等待} else {task->doit();  // 开始执行任务delete task;  //task指向的对象在WebServer中new出来,因此需要手动delete}}
}#endif

代码分析

首先需要注意,threadpool的实现使用了模板类,这样就需要把类的定义与成员函数的实现放在一个文件里,因为成员函数不能单独编译。

  • 关于m_stop的作用:可以发现m_stop的初值为false,这样线程就可以一直循环等待执行任务( 见ThreadPool::run()成员函数 ),但是m_stop的值改变的位置在析构函数中,线程池都退出了,改变m_stop的值还有什么用呢?其实,在将m_stop的值改变后,又调用了broadcast()来唤醒所有线程,这样所有被阻塞的线程就会开始执行,run()方法的循环条件是while( !m_stop ),这样其循环会被破坏,进而线程结束,被自动回收。// PS:然而只要主线程(暂且就这样叫了)退出( 不是通过pthread_exit()退出 ),那么所有线程就会被强制终止,同样也不会正常退出,所以线程池析构函数中唤醒所有线程并不会有什么作用,可能被唤醒的线程仍会在主线程结束前未执行完,仍为非正常退出。可以通过sleep()来环节,不过这样仍不会对正在执行任务的线程有很好的作用,因为任务执行时间未知。(可以考虑在析构函数中使用join()等待线程结束 )

  • 在任务队列进行添加任务、取任务、删除任务时,为了避免多个线程对任务队列同时操作,需要在进行修改时将任务队列加锁

  • 由于pthread_create第三个参数必须指向静态函数,要使用类成员函数和变量,只能通过:
    1) 类的静态对象
    2) 将类的对象作为参数传给静态函数
    这里通过第二种方法实现,将对象本身作为参数传到线程工作函数,在线程函数中进行类型强转获取调用对象中存储的数据。

  • 在从任务队列获取任务时:如果任务队列非空,就加锁获得队首任务,再将队首出队,将任务返回即可;如果任务队列为空,即无法获取任务,就会返回NULL,那么run()函数对应就会进入else后的语句,将线程进行阻塞,等待下一个任务的到来。

  • 关于在run() 中 delete task;的说明:由于所有任务都是webServer类中使用new创建的(详见我的Github),因为如果不适用new的话,在将任务加入队列后,webServer.cpp中任务会出作用域,进行析构,那么任务可能会在执行完之前被析构,造成错误,所以使用new创建。然而,new创建的对象不会自动释放,只能手动delete,因此在任务结束后进行delete,这样才能使其资源释放,同事可以调用Task的析构函数,关闭该任务对应的连接。


附:头文件locker.h

封装互斥锁与条件变量的类
// locker.h
#ifndef _LOCKER_H_
#define _LOCKER_H_#include <iostream>
#include <exception>
#include <pthread.h>
using namespace std;/* 线程锁 */
class MutexLocker {
private:pthread_mutex_t m_mutex;
public:MutexLocker() {  //初始化if( pthread_mutex_init( &m_mutex, NULL ) ) {cout << "mutex init errror __ 1\n";throw exception();}}~MutexLocker() {pthread_mutex_destroy( &m_mutex );}bool mutex_lock() {return pthread_mutex_lock( &m_mutex ) == 0;}bool mutex_unlock() {return pthread_mutex_unlock( &m_mutex );}
};/* 条件变量 */
class Cond {
private:pthread_mutex_t m_mutex;pthread_cond_t m_cond;
public:Cond() {if( pthread_mutex_init( &m_mutex, NULL ) ) {throw exception();}if( pthread_cond_init( &m_cond, NULL ) ) {pthread_cond_destroy( &m_cond );throw exception();}}~Cond() {pthread_mutex_destroy( &m_mutex );pthread_cond_destroy( &m_cond );}// 等待条件变量,cond与mutex搭配使用,避免造成共享数据的混乱bool wait() {pthread_mutex_lock( &m_mutex );int ret = pthread_cond_wait( &m_cond, &m_mutex );pthread_mutex_unlock( &m_mutex );return ret == 0;}// 唤醒等待该条件变量的某个线程bool signal() {return pthread_cond_signal( &m_cond ) == 0;}// 唤醒所有等待该条件变量的线程bool broadcast() {return pthread_cond_broadcast( &m_cond ) == 0;}
};#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/381876.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uva 1347——Tour

题意&#xff1a;给定n个点的坐标&#xff0c;设计一条路线&#xff0c;从左边的点出发&#xff0c;走到最右边的点然后返回&#xff0c;每个点除了起点和终点最多只能经历一次&#xff0c;求其中的最短路径。 思路&#xff1a;dp&#xff0c;可以考虑成2个人从起点出发&#x…

基于epoll+threadpool的webServer分析与实现

该webServer使用epollthreadpool实现&#xff0c;支持GET、POST方法&#xff0c;并添加CGI进行数据计算并返回网页信息&#xff0c;可以解析返回html、picture、mp3、js、css等文件&#xff0c;可以实现稳定的运行。 使用c编写。 源码请看我的Github。 流程简述 启动服务器&…

Flex2.0实现文件上传功能(服务器为ASP.NET)

简介&#xff1a;新的Flex2.0类库里提供了文件类&#xff0c;方便了上传/下载文件。下面的程序demo演示了Flex2.0生成flash来访问本地文件&#xff0c;在flash里上传用户选择的文件到服务器&#xff0c;flash客户端可以处理文件上传进度等多个事件&#xff0c;服务器端是C#写的…

UVA 116——Unidirectional TSP

题意&#xff1a;给定一个n*m的矩阵&#xff0c;从第一列任意位置向右&#xff0c;右下&#xff0c;右上走一格&#xff0c;可以循环&#xff0c;要求经过的数字的和最小。 思路&#xff1a;记忆化搜索。每次有三个状态&#xff0c;从这三个状态中选择出最优的一个&#xff0c;…

【高性能定时器】 时间轮

时间轮 简述 顾名思义&#xff0c;时间轮就像一个轮子&#xff0c;在转动的时候外界会指向轮子不同的区域&#xff0c;该区域就可以被使用。因此只要将不同时间的定时器按照一定的方法散列到时间轮的不同槽&#xff08;即时间轮划分的区域&#xff09;之中&#xff0c;就可以实…

知识管理的三种策略

知识管理的三种策略 面向流程的知识管理战略&#xff0d;实施篇&#xff08;上&#xff09;(AMT研究院 周瑛 编译) 转载于:https://www.cnblogs.com/yangfada/archive/2006/01/24/322381.html

java 考试复习整理——JAVA类和类成员的修饰符

今天整理了一下以前学的JAVA的类和类成员的修饰符&#xff0c;考试的时候可能会考到。一&#xff1a;访问修饰符&#xff1a;1.省略访问修饰符&#xff1a;具有默认的访问特性&#xff0c;即具有包访问特性&#xff0c;只能被同一个包中的类使用。2.public访问修饰符&#xff1…

【安利】程序猿作图神器 - Graphviz

还在为在linux下画二叉树等图苦恼吗&#xff0c;现在就安利一波linux程序猿的作图神器——Graphviz。&#xff08;本来在写其他东西&#xff0c;刚好要绘图&#xff0c;强行插入一篇blog&#xff09; Graphviz (Graph Visualization Software) 是一个由AT&T实验室启动的开源…

仿ISQL功能的实现,可以实现批处理功能

具体请见下载文件&#xff1a;/Files/bigmouthz/DNet写的数据库isql执行程序&#xff08;含源码&#xff09;.rar部分代码如下&#xff1a; DBCore.DataBaseVisitor.AbsDBHelper dbhelper null ; private void bt_DBLink_Click(object sender, System.EventArgs e) { try …

uva 11400——Lighting System Design

题意&#xff1a; 给定一些灯泡&#xff0c;每种灯泡有不同的电压v&#xff0c;电源费用k&#xff0c;每个灯泡的费用c&#xff0c;所需的灯泡的数量L&#xff0c;同种灯泡可以使用相同电源&#xff0c;问最小花费。 思路&#xff1a;dp&#xff0c;每种灯泡要么选要么不选两种…

【高性能定时器】时间堆(最小堆)

最小堆及其应用&#xff1a;时间堆 最小堆及其应用&#xff1a;时间堆 一、 堆1. 概念2. 最小堆的实现3. 性质4. 代码 二、时间堆1. 概念简述2. 实现细节3. 代码 一、 堆 1. 概念 堆是一种经过排序的完全二叉树&#xff0c;其中任一非终端节点的数据值均不大于&#xff08;或…

uva 11584——Partitioning by Palindromes

题意&#xff1a;给定一个字符串&#xff0c;把该字符串划分为最少的回文串。 思路&#xff1a;dp&#xff0c;到达i点的回文串长度都存起来&#xff0c;那么dp[i]min(以i为结尾的最短的回文串长度&#xff09;。 code&#xff1a; #include <bits/stdc.h> using namespa…

【操作系统】生产者消费者问题

生产者消费者模型 文章目录生产者消费者模型 [toc]一、 生产者消费者问题二、 问题分析三、 伪代码实现四、代码实现&#xff08;C&#xff09;五、 互斥锁与条件变量的使用比较一、 生产者消费者问题 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer problem&…

2006年2月8日 再见,Borland

昨天早上从David I的Blog上看到了Borland出售IDE产品线转而全面发展ALM的消息,既感到惋惜又似乎感到新的希望 之所以惋惜是因为Delphi是我第一个喜欢的语言&#xff0c;也是Delphi第一次让我对编程产生兴趣&#xff0c;还记得我的第一个小软件就是Delphi5.0做的&#xff0c;而如…

【计算机网络】三次握手与四次挥手

三次握手与四次挥手 通过TCP/IP协议的学习&#xff0c;我们可以知道TCP协议是一种面向连接的、可靠的传输协议。其中&#xff0c;为了保证客户端与服务器连接的有效性&#xff0c;就有了本篇文章所要介绍的“三次挥手”&#xff1b;而“四次挥手”则是为了保证连接的正确断开。…

uva 10003——Cutting Sticks

题意&#xff1a;给定一长为L的木棍和n个切割点&#xff0c;每次切割的费用为切割的长度&#xff0c;求最小的费用。 思路&#xff1a;dp,子问题是区间&#xff08;i&#xff0c;j&#xff09;的最小费用&#xff0c;临界是&#xff08;i,j)只有一个切割点。dp[i,j]min(dp[i,k]…

小叔叔又飞走了

小叔叔在飞走之前&#xff0c;昨天晚上&#xff0c;给我们看了&#xff0c;他回老家拍下的&#xff0c;家里每个人的视频&#xff0c;&#xff08;前面这半句话说得好累&#xff0c;不过应该不是病句&#xff09;让我们看了动情又亲切&#xff0c;至少我是这么觉得。一个大家庭…

系统调用中断(EINTR)与SIGCHLD信号的处理

一、被中断的系统调用(EINTR)的理解1. 慢系统调用是&#xff1f;2. 慢系统调用的类别3. EINTR产生的原因5. 一般处理方法 二、SIGCHLD信号的处理1. SIGCHLD信号的产生2. SIGCHLD信号的处理3. 不处理SIGCHLD的后果 三、示例代码 一、被中断的系统调用(EINTR)的理解 1. 慢系…

SMO写的查看数据库信息的代码

要分析一个比较大的数据库&#xff0c;里面的表太多了&#xff0c;虽然是中文命名&#xff0c;但在2005的Management Studio中查看还是比较麻烦&#xff0c;比如&#xff0c;我想查看具有相同字段名称的表的情况就不好办。于是用SMO写了这个东东。代码比较乱&#xff0c;没有进…

uva 1626——Brackets sequence

题意&#xff1a;定义满足 1.空序列 2.&#xff08;&#xff09;&#xff08;X&#xff09;及括号和其括起来的合法序列 3.【】要求和&#xff08;&#xff09;相同 都是合法的串。 然后给定一段序列&#xff0c;求添加最小的&#xff08;&#xff09;或【】使得序列合法。…