线程池基本原理

在传统服务器结构中,常是有一个总的监听线程监听有没有新的用户连接服务器,每当有一个新的用户进入,服务器就开启一个新的线程用户处理这 个用户的数据包。这个线程只服务于这个用户,当用户与服务器端关闭连接以后,服务器端销毁这个线程。

然而频繁地开辟与销毁线程极大地占用了系统的资源,而且在大量用户的情况下,系统为了开辟和销毁线程将浪费大量的时间和资源。线程池提供了一个解决外部大量用户与服务器有限资源的矛盾。

线程池和传统的一个用户对应一个线程的处理方法不同,它的基本思想就是在程序开始时就在内存中开辟一些线程,线程的数目是固定的,他们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从“池子”中选择一个空闲的线程为新的客户请求服务,服务完毕后,线程进入空闲线程池中。如果没有线程空闲的话,就将数据包暂时积累, 等待线程池内有线程空闲以后再进行处理。通过对多个任务重用已经存在的线程对象,降低了对线程对象创建和销毁的开销。当客户请求 时,线程对象已经存在,可以提高请求的响应时间,从而整体地提高了系统服务的表现。

线程池应用实例

一般来说实现一个线程池主要包括以下几个组成部分:

1)线程管理器:用于创建并管理线程池。

2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用 CPU,占用较小的内存空间。

3)任务接口:每个任务必须实现的接口,当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的),把任务抽象出来形成接口,可以做到线程池与具体的任务无关。

4)任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的是队列,主要运用先进先出原理,另外一种是链表之类的数据结构,可以动态的为它分配内存空间,应用中比较灵活,此教程就是用到的链表。

什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了。如果线程创建和销毁时间相比任务执行时间可以忽略不计,则没有必要使用线程池了。

线程池实现示例代码如下:

thread_pool.hpp 的示例代码:

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using namespace std;struct Task//任务
{
private:int _val;
public:Task(){}Task(int val):_val(val){}void work()//获得任务后在这里进行判断{for(int j=2;j < _val;j++){if(_val%j==0){cout<<"thread:"<<pthread_self()<<"   "<<_val<<"不是素数"<<endl;return;}}cout<<"thread:"<<pthread_self()<<"   "<<_val<<"是素数"<<endl;return;}
};class ThreadPool//线程池
{
private:bool _thread_quit_flag;//线程退出标志,如果主线程发送了信号,就将其置为trueint _thread_num;//线程池线程数量queue<Task*> q;//任务队列,存放指针pthread_mutex_t lock;//保护任务队列的锁pthread_cond_t cond;//条件变量,当没有任务时线程池的线程挂起,当有任务时唤醒线程池线程public:void ThreadLock(){//锁pthread_mutex_lock(&lock);}void ThreadUnlock(){//解锁pthread_mutex_unlock(&lock);}bool IsEmpty(){//判断任务队列为空return q.size()==0;}void Threadwait(){//没有任务线程池的线程挂起pthread_cond_wait(&cond,&lock);}void Threadwakeup(){//当主线程添加了任务就唤醒线程池线程pthread_cond_signal(&cond);}void ThreadWakeAll(){//最后发送退出信号时,将所有线程唤醒,然后让其退出pthread_cond_broadcast(&cond);//唤醒所有线程}void ThreadQuit(){//线程退出函数pthread_exit(nullptr);}public:ThreadPool(int thread_num=5):_thread_quit_flag(false),_thread_num(thread_num)//flag默认设置为flase,不退出{pthread_mutex_init(&lock,nullptr);pthread_cond_init(&cond,nullptr);}~ThreadPool(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}static void* Route(void* arg)//所有的线程都要执行这样的流程:先判断是否有任务,有就执行,没有就挂起{pthread_detach(pthread_self());//线程 ThreadPool* this_p=(ThreadPool*)arg;//某个线程的this指针while(1){this_p->ThreadLock();//锁定任务队列while(this_p->_thread_quit_flag==false && this_p->IsEmpty())//如果没有发出退出信号并且队列为空,那么就让线程挂起{this_p->Threadwait();//当线程苏醒时(可能接收到了广播信号或者位于线程队列它被下一个唤醒了),就会从挂起的地方醒来,继续向下执行}if((this_p->_thread_quit_flag==false && !this_p->IsEmpty()) || (this_p->_thread_quit_flag==true && !this_p->IsEmpty()))//醒来的原因不管是否是因为接收到了退出信号,反正只要有任务就得先执行完{Task t;this_p->Get(t);this_p->ThreadUnlock();//注意不要在临界资源内做任务,效率很低t.work();//获得任务后进行计算}//如果接受到信号且队列已经Wie空了,那么就退出线程else if(this_p->_thread_quit_flag==true && this_p->IsEmpty())//如果醒来的原因是因为接受到了退出信号,而且任务队列中已经没有任务了,那么就退出线程{this_p->ThreadUnlock();this_p->ThreadQuit();//退出时不要忘记解锁,否则将来只有一个线程能推出,其余线程无法退出}}
}void ThreadPoolInit()//风险操作不要在构造函数中写,该函数用于创建线程池线程,并让线程执行Route流程{pthread_t t;for(int i=0;i < _thread_num;i++){pthread_create(&t,nullptr,Route,this);//这里最后一个参数传入了this指针,相应的Route函数也是静态成员函数//如果不传入this指针,那么当这个参数就会和非静态成员函数抢this指针的位置,因此会造成参数过多的问题//所以在这里传入this,然后在形参中使用this调用自己的成员函数即可}}void ThreadPoolQuit()//线程池退出{_thread_quit_flag=true;//将结束标志置为trueThreadWakeAll();//唤醒所有线程}void Get(Task& t)//线程获取任务{Task* out=q.front();//注意是指针q.pop();t=*out;//这是解引用}void Put(Task& t)//主线程放任务{ThreadLock();q.push(&t);//注意这是取地址ThreadUnlock();Threadwakeup();//当放了一个任务后立马唤醒线程,这里一般情况下不要唤醒所有线程,会产生掠群效应,影响稳定性}
};

下面是测试代码:

test.c 的示例代码:

#include "thread_pool.hpp"
#include <unistd.h>
int main()
{ThreadPool* tp = new ThreadPool();tp->ThreadPoolInit();int x = 3;while(1){Task t(x);tp->Put(t);x++;if(x==10)//当10时退出线程池{tp->ThreadPoolQuit();//break;}sleep(1);}return 0;
}

makefile

src = $(wildcard *.c)
targets = $(patsubst %.c, %, $(src))CC = g++
CFLAGS = -Wall -g -lpthreadall:$(targets)$(targets):%:%.c$(CC) $< -o $@ $(CFLAGS).PHONY:clean all
clean:-rm -rf $(targets) test ps.out temp

运行结果如下:

先解锁后执行,让生产者继续生产,提高效率。

参考:

1. https://blog.csdn.net/qq_39183034/article/details/116310114

Linux系统编程 -- 线程池操作相关推荐

  1. Linux系统编程——线程池

    http://blog.csdn.net/tennysonsky/article/details/46490099# 线程池基本原理 在传统服务器结构中,常是有一个总的监听线程监听有没有新的用户连接服 ...

  2. Linux系统编程——线程私有数据

    在多线程程序中.常常要用全局变量来实现多个函数间的数据共享.因为数据空间是共享的,因此全局变量也为全部线程共同拥有. 測试代码例如以下: #include <stdio.h> #inclu ...

  3. Linux系统编程-文件的操作

    Linux系统编程-文件操作 前言: Linux 中所有内容都是以文件的形式保存和管理的,即一切皆文件,普通文件是文件,硬件设备(键盘.监视器.硬盘.打印机)是文件,就连套接字(socket).网络通 ...

  4. Linux系统编程——线程

    一.线程概念 基础 线程又称LWP:light weight process 轻量级的进程,(在linux环境下)本质仍是进程.进程:独立地址空间,拥有PCB 线程:有独立的PCB,但没有独立的地址空 ...

  5. Linux系统编程——线程(1)

    目录 线程概要 Linux内核线程实现原理 线程的共享/不共享资源 线程优缺点 线程控制原语 pthread_self pthread_create pthread_exit pthread_join ...

  6. Linux系统编程.NO7——目录操作函数

    5. 目录文件管理函数 5.1. 目录文件概念 目录在Linux中也是文件,也有对应的文件指针,每次读目录之前也要先打开目录,然后目录指针就出现了,第一次读就是第一个文件的信息,然后第二次读就是第二个 ...

  7. Linux系统编程 | 01 -文件操作

    一.文件操作方法 linux中有两种方法可以操作文件:系统调用和c库函数. 1. 什么是系统调用? 由操作系统实现并提供给外部应用程序的编程接口(API),是应用程序同系统之间数据交互的桥梁. C标准 ...

  8. 入门Linux系统编程--网络编程

    文章目录 一.网络编程 1.socket服务端代码实现(无连接客户端) 6.socket服务端代码实现(连接客户端) 7.socket客户端代码实现 8.实现双方聊天 9.多方消息收发 二.往期文章 ...

  9. linux线程并不真正并行,Linux系统编程学习札记(十二)线程1

    Linux系统编程学习笔记(十二)线程1 线程1: 线程和进程类似,但是线程之间能够共享更多的信息.一个进程中的所有线程可以共享进程文件描述符和内存. 有了多线程控制,我们可以把我们的程序设计成为在一 ...

  10. Linux系统编程(九)线程同步

    Linux系统编程(九)线程同步 一.什么是线程同步? 二.互斥量 三.条件变量 pthread_cond_wait函数 pthread_cond_signal函数 生产者和消费者模型 一.什么是线程 ...

最新文章

  1. javascript的族家族史
  2. fatal: unable to access ‘‘: TCP connection reset by peer
  3. 操作系统(三十五)连续分配管理方式
  4. HTML5 Canvas 自定义笔刷
  5. html5画电池状态,HTML5的一个显示电池状态的API简介
  6. Android中文API(122) —— AudioRecord
  7. “中国音乐金钟奖”落地成都 今年起将连续举办三届
  8. Git : 每一行命令都算数
  9. JAVA原码反码补码
  10. 棒!使用.NET Core构建3D游戏引擎
  11. matlab 随机森林 分类,randomforest-matlab 随机森林分类器的MATLAB代码 - 下载 - 搜珍网...
  12. vue中用echats绘制中国地图
  13. win10启用php_zip,win10右键没有压缩文件选项怎么办
  14. WinCE全屏手写输入法
  15. 非负矩阵分解小白入门
  16. 面向开发人员的 27种Vuejs开发工具
  17. 减肥记(减肥topic总结篇)
  18. 利用3000fps把人脸面部抠出来(一)
  19. Ubuntu18.04 配置nvidia 460驱动、cuda10.1和cudnn7.6.5
  20. 06-1-SVM原理

热门文章

  1. HTML5 — 知识总结篇《III》【文本元素】
  2. 指定某个git的版本代码拉取新的分支
  3. 定义工作,解读自我——IT帮2019年2月线下活动回顾
  4. 关于CUDA中cutil的一些问题
  5. 【Photoshop教程】转发:Photoshop把帅哥转成超酷的阿凡达
  6. 一步一步学Ruby(五): Class, Module, Object,Kernel的关系
  7. 思科EIGRP配置及基本讲解
  8. HCIE-Security Day13:防火墙双机热备实验(一)防火墙直路部署,上下行连接交换机
  9. 使用python连接eNSP中交换机并添加配置
  10. IS-IS详解(十五)——IS-IS 认证