【池化技术】线程池技术原理和C语言实现

2023-05-16

文章目录

    • 一、基础概念
      • 进程和线程
      • 多进程和多线程区别
    • 二、线程池技术

一、基础概念

在讲线程池技术之前,我们先对操作系统中的一些基础概念,比如进程、线程、线程的创建与销毁等进行说明。

进程和线程

进程
一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

线程
进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。

public class MultiThread {
	public static void main(String[] args) {
		// 获取 Java 线程管理 MXBean
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
		// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
		// 遍历线程信息,仅打印线程 ID 和线程名称信息
		for (ThreadInfo threadInfo : threadInfos) {
			System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
		}
	}
}

上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):

[6] Monitor Ctrl-Break //监听线程转储或“线程堆栈跟踪”的线程
[5] Attach Listener //负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //在垃圾收集前,调用对象 finalize 方法的线程
[2] Reference Handler //用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收的线程
[1] main //main 线程,程序入口

从上面的输出内容可以看出:一个 Java 程序的运行是 main 线程和多个其他线程同时运行。

进程与线程的区别总结
线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

多进程和多线程区别

多进程:操作系统中同时运行的多个程序

多线程:在同一个进程中同时运行的多个任务

举个例子,多线程下载软件,可以同时运行多个线程,但是通过程序运行的结果发现,每一次结果都不一致。 因为多线程存在一个特性:随机性。造成的原因:CPU在瞬间不断切换去处理各个线程而导致的,可以理解成多个线程在抢CPU资源。

多线程提高CPU使用率

多线程

多线程并不能提高运行速度,但可以提高运行效率,让CPU的使用率更高。但是如果多线程有安全问题或出现频繁的上下文切换时,运算速度可能反而更低。

二、线程池技术

线程池(thread pool)技术是指能够保证所创建的任一线程都处于繁忙状态,而不需要频繁地为了某一任务而创建和销毁线程,因为系统在创建和销毁线程时所耗费的cpu资源很大。如果任务很多,频率很高,为了单一一个任务而起线程而后销线程,那么这种情况效率相当低下的。线程池技术就是用于解决这样一种应用场景而应运而生的。

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

线程池技术的工作原理:在起先就创建一定数量的线程以队列形式存在,并为其分配一个工作队列,当工作队列为空时,表示没有任务,此时所有线程挂起等待新的工作到来。当新的工作到来时,线程队列头开始执行这个任务,然后依次是第二、第三个线程执行新到来的任务,当其中某个线程处理完任务后,那么该线程立马开始接受任务分派,从而让所有线程都处于忙碌的状态,提高并行处理效率。

线程池技术是一种典型的生产者-消费者模型。因此,无论用哪种语言实现,只要遵循其原理本身就能够很好的工作了。那么实现线程池技术我们需要考虑到哪些技术性的问题?

C语言线程池技术的实现:

需要考虑的技术问题一,线程池应该包含哪些成员变量。

既然要开一定数量的线程,那么这个“一定数量(max_thread_num)”必定是线程池的一个成员。

如何表示一个线程池是否已经关闭?如果关闭那么必需要立马释放资源。所以“是否关闭(shutdown)”也是一个成员。

创建线程需要有id,必需要为每个线程准备一个id,所以需要一个id数组,其长度就是max_thread_num。

线程锁,用以保证对线程操作时的互斥性。所以需要一个锁,queue_lock。

条件变量(condition_variable),这里使用条件变量主要是为了广播任务到来的消息给所有线程。当有处于空闲的线程,则由此线程

接受任务分派。所以需要一个条件变量queue_ready。

最为重要的就是任务本身,也就是工作。那么工作本身又需要哪几个成员变量?首先肯定是任务入口,routine函数;

其次是routine函数的参数args;再次任务是以队列存在着的,所以任务本身应该包含一个next。

需要考虑的技术问题二,线程池应该包含哪些api。

一、创建线程池,create_tpool

二、销毁线程池,destroy_tpool

三、分派任务,add_task_2_tpool

基于上述分析,我们可以先构造头文件。

tpool.h

#ifndef T_POOL
#define T_POOL
 
#include <pthread.h>
#include <ctype.h>
 
typedef struct tpool_work{
   void* (*work_routine)(void*); //function to be called
   void* args;                   //arguments 
   struct tool_work* next;
}tpool_work_t;
 
typedef struct tpool{
   size_t               shutdown;       //is tpool shutdown or not, 1 ---> yes; 0 ---> no
   size_t               maxnum_thread; // maximum of threads
   pthread_t            *thread_id;     // a array of threads
   tpool_work_t*        tpool_head;     // tpool_work queue
   pthread_cond_t       queue_ready;    // condition varaible
   pthread_mutex_t      queue_lock;     // queue lock
}tpool_t;
 
 
/***************************************************
*@brief:
*       create thread pool
*@args:   
*       max_thread_num ---> maximum of threads
*       pool           ---> address of thread pool
*@return value: 
*       0       ---> create thread pool successfully
*       othres  ---> create thread pool failed
***************************************************/
 
int create_tpool(tpool_t** pool,size_t max_thread_num);
 
/***************************************************
*@brief:
*       destroy thread pool
*@args:
*        pool  --->  address of pool
***************************************************/
void destroy_tpool(tpool_t* pool);
 
/**************************************************
*@brief:
*       add tasks to thread pool
*@args:
*       pool     ---> thread pool
*       routine  ---> entry function of each thread
*       args     ---> arguments
*@return value:
*       0        ---> add ok
*       others   ---> add failed        
**************************************************/
int add_task_2_tpool(tpool_t* pool,void* (*routine)(void*),void* args);
 
#endif//tpool.h

需要考虑的技术问题三,线程池的所有权应该交予谁。

这里我们需要考虑到,将线程池封装成一个so库是比较好的想法,那么,线程池的所有权就应该交予调用它的函数。所以我这里采取的就是这个方法。

tpool.c

#include "tpool.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
 
 
static void* work_routine(void* args)
{
   tpool_t* pool = (tpool_t*)args;
   tpool_work_t* work = NULL;
 
   while(1){
        pthread_mutex_lock(&pool->queue_lock);
        while(!pool->tpool_head && !pool->shutdown){ // if there is no works and pool is not shutdown, it should be suspended for being awake
            pthread_cond_wait(&pool->queue_ready,&pool->queue_lock);
        }
 
        if(pool->shutdown){
           pthread_mutex_unlock(&pool->queue_lock);//pool shutdown,release the mutex and exit
           pthread_exit(NULL);
        }
 
        /* tweak a work*/
        work = pool->tpool_head;
        pool->tpool_head = (tpool_work_t*)pool->tpool_head->next;
        pthread_mutex_unlock(&pool->queue_lock);
 
        work->work_routine(work->args);
 
        free(work);
   }
return NULL;
}
 
int create_tpool(tpool_t** pool,size_t max_thread_num)
{
   (*pool) = (tpool_t*)malloc(sizeof(tpool_t));
   if(NULL == *pool){
        printf("in %s,malloc tpool_t failed!,errno = %d,explain:%s\n",__func__,errno,strerror(errno));
        exit(-1);
   }
   (*pool)->shutdown = 0;
   (*pool)->maxnum_thread = max_thread_num;
   (*pool)->thread_id = (pthread_t*)malloc(sizeof(pthread_t)*max_thread_num);
   if((*pool)->thread_id == NULL){
        printf("in %s,init thread id failed,errno = %d,explain:%s",__func__,errno,strerror(errno));
        exit(-1);
   }
   (*pool)->tpool_head = NULL;
   if(pthread_mutex_init(&((*pool)->queue_lock),NULL) != 0){
        printf("in %s,initial mutex failed,errno = %d,explain:%s",__func__,errno,strerror(errno));
        exit(-1);
   }
 
   if(pthread_cond_init(&((*pool)->queue_ready),NULL) != 0){
        printf("in %s,initial condition variable failed,errno = %d,explain:%s",__func__,errno,strerror(errno));
        exit(-1);
   }
 
   for(int i = 0; i < max_thread_num; i++){
        if(pthread_create(&((*pool)->thread_id[i]),NULL,work_routine,(void*)(*pool)) != 0){
           printf("pthread_create failed!\n");
           exit(-1);
        }
   }
return 0;
}
 
void destroy_tpool(tpool_t* pool)
{
   tpool_work_t* tmp_work;
 
   if(pool->shutdown){
        return;
   }
   pool->shutdown = 1;
 
   pthread_mutex_lock(&pool->queue_lock);
   pthread_cond_broadcast(&pool->queue_ready);
   pthread_mutex_unlock(&pool->queue_lock);
 
   for(int i = 0; i < pool->maxnum_thread; i++){
        pthread_join(pool->thread_id[i],NULL);
   }
   free(pool->thread_id);
   while(pool->tpool_head){
        tmp_work = pool->tpool_head;
        pool->tpool_head = (tpool_work_t*)pool->tpool_head->next;
        free(tmp_work);
   }
 
   pthread_mutex_destroy(&pool->queue_lock);
   pthread_cond_destroy(&pool->queue_ready);
   free(pool);
}
 
int add_task_2_tpool(tpool_t* pool,void* (*routine)(void*),void* args)
{
   tpool_work_t* work,*member;
 
   if(!routine){
        printf("rontine is null!\n");
        return -1;
   }
 
   work = (tpool_work_t*)malloc(sizeof(tpool_work_t));
   if(!work){
        printf("in %s,malloc work error!,errno = %d,explain:%s\n",__func__,errno,strerror(errno));
        return -1;
   }
 
   work->work_routine = routine;
   work->args = args;
   work->next = NULL;
 
   pthread_mutex_lock(&pool->queue_lock);
   member = pool->tpool_head;
   if(!member){
        pool->tpool_head = work;
   }
   else{
        while(member->next){
           member = (tpool_work_t*)member->next;
        }
        member->next = work;
   }
 
   //notify the pool that new task arrived!
   pthread_cond_signal(&pool->queue_ready);
   pthread_mutex_unlock(&pool->queue_lock);
return 0;
}

demo.c

#include "tpool.h"
#include <stdio.h>
#include <unistd.h>
#include <time.h>
 
void* fun(void* args)
{
   int thread = (int)args;
   printf("running the thread of %d\n",thread);
return NULL;
}
 
int main(int argc, char* args[])
{
   tpool_t* pool = NULL;
   if(0 != create_tpool(&pool,5)){
        printf("create_tpool failed!\n");
        return -1;
   }
 
   for(int i = 0; i < 1000; i++){
        add_task_2_tpool(pool,fun,(void*)i);
   }
   sleep(2);
   destroy_tpool(pool);
return 0;
}

Makefile

pool: tpool.c demo.c
        gcc tpool.c demo.c -o pool -lpthread -std=c99 
 
clean:
        rm ./pool

其他的一些C语言实现可以参考下面这些文章:

C语言实现线程池技术

C语言实现线程池

线程池原理及C语言实现线程池

C语言实现线程池功能

进程和线程的区别(超详细)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【池化技术】线程池技术原理和C语言实现 的相关文章

随机推荐

  • Redis常用命令及C/C++调用Redis接口详解

    文章目录 一 Redis常用命令启动Redis服务端启动Redis客户端Redis常用命令详解键 xff08 Key xff09 命令Hash 命令String 命令List 命令Set 命令Zset 命令 二 C C 43 43 调用接口
  • Redis数据结构2

    继续放一下小林coding的文章 xff1a 为了拿捏 Redis 数据结构 xff0c 我画了 40 张图 xff08 完整版 xff09
  • Redis博客、教程学习资料汇总(持续更新)

    在这篇博客中 xff0c 总结一下写过的博客和一些好的学习资料 博客 xff1a Redis基础 xff0c Linux下安装Redis和hredis xff0c C 43 43 调用Redis xff0c Redis中字符串设计 Redi
  • memcached在linux上的安装,C/C++调用memcached,memcached与Redis的区别

    文章目录 一 memcached在linux上的安装安装 Memcached自动安装源代码安装 Memcached 运行 xff08 1 xff09 作为前台程序运行 xff1a xff08 2 xff09 作为后台服务程序运行 xff1a
  • 【C/C++服务器开发】事件驱动、事件驱动架构、事件驱动编程及设计模式

    文章目录 一 事件驱动二 事件驱动编程事件驱动和异步IO看图说话讲事件驱动模型 三 C C 43 43 实现事件驱动四 常用的C C 43 43 事件驱动库 一 事件驱动 首先我们来看看百度百科的介绍 所谓事件驱动 xff0c 简单地说就是
  • VxWorks消息队列详解

    文章目录 一 前言二 VxWorks消息队列模块详解三 代码实例 一 前言 最近看了点事件驱动编程 了解到在时间驱动编程中 xff0c 重要的是一个事件收集器 一个事件发送器和一个事件处理器 这让我联想到VxWorks中的消息队列 xff0
  • VxWorks/tornado中怎么调试及WindSh常用命令行详解

    一 调试 VxWorks5 5 tornado2 2作为一款极为远古的嵌入式实时操作系统和IDE xff0c 软件的界面和功能都是极为原始的 在这种情况下 xff0c 如果我们要对软件进行调试该怎么操作呢 xff1f tornado调试分为
  • 如何长期输出优质内容?我是如何做到的?

    分享一篇站长的文章 xff1a 如何长期输出优质内容 xff1f 我是如何做到的 xff1f 创业 7 年时间里 xff0c 我一直在运营C语言中文网 xff0c 创作和参与了 40 多套编程教材 xff0c 累计阅读人次超过 2000 万
  • Dev C++调试程序方法详解

    分享一篇站长的关于调试的文章 xff1a Dev C 43 43 调试程序方法详解 Dev C 43 43 算是兼容性很好使用很方便的IDE了 xff0c 记录一下调试方法 xff0c 以备不时之需 所谓调试程序 xff0c 就是控制编译器
  • 为什么看到这么多人不推荐C++?

    转一个回答 链接 xff1a https www zhihu com question 22853451 answer 2084675682 其实现在也越来越发现 xff0c C 43 43 老是在弄那些语言特性 xff0c 当然有一些好用
  • 模型部署之NVIDIA AGX Xavier 配置和使用Torch,ONNX,TensorRT做模型推理

    Do not blindly trust anything I say try to make your own judgement 目录 1 安装CUDA cudnn tensorrt 2 配置Torch 3 配置ONNX 4 配置Ten
  • 什么是包管理器,C++ 有没有像 pip、npm、gem 一样的包管理工具?

    文章目录 一 包管理器什么是包 xff1f RPM包管理器dpkg包管理器二 C 43 43 有没有像 pip npm gem 一样的包管理工具 xff1f 三 C 43 43 包管理器有哪些1 Conan https github com
  • 二进制文件和库之间有什么区别?

    问题 我正在尝试了解文件系统层次结构标准 我已经查找了二进制文件和库 xff0c 并且据我目前的理解 xff1a 二进制文件是二进制格式的计算机可读代码文件 xff0c 它们直接用位控制CPU和处理器 为了方便起见 xff0c 库是可由各种
  • 如何用OneNote、Typora、Notion构建知识体系?

    我其实用OneNote和typora已经很久了 xff0c 因此很想来说说我使用这两款软件的方法和感想 xff0c 再加上最近听说Typora正式版要开始收费了 xff08 14 99 xff09 xff0c 还有一个国外很火的笔记Noti
  • 【程序员学理财】为什么要开这个专栏?

    李嘉诚有一句名言 xff1a 30岁以前人要靠体力 智力赚钱 xff0c 30岁之后要靠钱赚钱 要靠打工实现财富自由几乎是不可能的事 xff0c 靠钱赚钱 xff0c 是实现财富快速积累的唯一方式 当然要靠钱赚钱 xff0c 也是需要原始积
  • 【程序员读论文】为什么要读论文?

    一 标题 标题虽然说是读论文 xff0c 但其实不是很准确 xff0c 而是读以论文为代表的具有高密度知识的内容 xff0c 包括论文 xff08 硕博毕业论文 xff0c 期刊会议论文 xff09 xff0c 书籍 xff08 全面但知识
  • 【程序员学英语】翻译编程帮助手册和英文问答

    虽然PHP是最好的语言 xff0c 但是英语才是每个程序员都应该熟练掌握的语言 学习一门语言包括听 说 读 写 xff0c 对于每个在国内的程序员来说 xff0c 掌握基本的读 写是最基本的要求 因此这个专栏大概会专注于翻译帮助手册和英文问
  • ARINC429总线基础

    再来一份 xff0c 相互补充
  • 【池化技术】内存池技术原理和C语言实现

    文章目录 一 基础概念1 一个可执行程序占用的内存分为哪几个区 xff1f 一个进程的虚拟内存区域有哪些 xff1f 2 静态内存分配和动态内存分配 二 malloc实现原理malloc内存分配 xff08 下面算是正常一般的情况了 xff
  • 【池化技术】线程池技术原理和C语言实现

    文章目录 一 基础概念进程和线程多进程和多线程区别 二 线程池技术 一 基础概念 在讲线程池技术之前 xff0c 我们先对操作系统中的一些基础概念 xff0c 比如进程 线程 线程的创建与销毁等进行说明 进程和线程 进程 一个在内存中运行的