深入理解线程池

一、池化技术

池化技术指的是提前准备一些资源,在需要时可以重复使用这些预先准备的资源。

在系统开发过程中,我们经常会用到池化技术。通俗的讲,池化技术就是:把一些资源预先分配好,组织到池中,之后的业务使用资源从对象池中获取,使用完后放回到对象池中。这样做带来几个明显的好处:

  • 资源重复使用减少了资源分配释放过程中的系统消耗
  • 可以对资源的整体使用限制
  • 池化技术分配对象池,通常会集中分配,这样有效避免碎片化的问题。

常见池化技术使用:

  • 进程池:适合数据独立处理;
  • 线程池:适合数据交互频繁处理;
  • 内存池
  • 连接池

二、Master-Worker模式

Master-Worker 模式是常用的并行设计模式核心思想是,系统由两个角色组成,MasterWorkerMaster 负责接收分配任务,Worker 负责处理子任务。任务处理过程中,Master 还负责监督任务进展和Worker的健康状态

Master-Worker 模式满足于可以将大任务划分为小任务场景,是一种分而治之设计理念。通过多线程多进程多机器的模式,可以将小任务处理分发给更多的CPU处理,降低单个CPU的计算量,通过并发/并行提高任务的完成速度,提高系统的性能。

Master 对任务进行切分,并放入任务队列;然后,触发 Worker 处理任务。

实际操作中,任务的分配有多种形式,如:

1、Master 主动拉起 Workder 进程池或线程池,并将任务分配给 Worker;

2、Worker 主动领取任务,这样的 Worker 一般是常驻进程

3、Master 做任务的接收切分结果统计指定 Worker 的数量性能指标,但不参与 Worker 的实际管理,而是交由第三方调度监控调度 Worker。这是一种解耦的方式。

Master-Worker 模式各角色关系如下图:
在这里插入图片描述

三、线程基本概念

1、线程状态

1、准备:等待可用的CPU资源,其他条件一切准备好。当线程被pthread_create创建时或者阻塞状态结束后就处于准备状态。

2、运行 :线程已经获得CPU的使用权,并且正在运行,在多核心的机器中同时存在多个线程正在运行。如果这种情况不加以控制,会造成整个程序没响应。

3、阻塞:指一个线程在执行过程中暂停,以等待某个条件的触发。

4、终止:线程已经从回调函数中返回,或者调用pthread_exit返回,或者被强制终止。
在这里插入图片描述

2、互斥锁

线程中使用互斥锁用来保护关键代码段,以确保其独占式的访问。POSIX互斥锁相关函数主要有以下5个:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • pthread_mutex_init用于初始化互斥锁,mutexattr用于指定互斥锁的属性,若为NULL,则表示默认属性。除了用这个函数初始化互斥所外,还可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • pthread_mutex_destroy用于销毁互斥锁,以释放占用的内核资源,销毁一个已经加锁的互斥锁将导致不可预期的后果;
  • pthread_mutex_lock以原子操作给一个互斥锁加锁。如果目标互斥锁已经被加锁,则pthread_mutex_lock则被阻塞,直到该互斥锁占有者把它给解锁;
  • pthread_mutex_trylock和pthread_mutex_lock类似,不过它始终立即返回,而不论被操作的互斥锁是否加锁,是pthread_mutex_lock的非阻塞版本。当目标互斥锁未被加锁时,pthread_mutex_trylock进行加锁操作;否则将返回EBUSY错误码。注意:这里讨论的pthread_mutex_lock和pthread_mutex_trylock是针对普通锁而言的,对于其他类型的锁,这两个加锁函数会有不同的行为;
  • pthread_mutex_unlock以原子操作方式给一个互斥锁进行解锁操作。如果此时有其他线程正在等待这个互斥锁,则这些线程中的一个将获得它;

pthread_mutexattr_t 结构体定义了一套完整的互斥锁属性。线程库提供了一系列函数来操作 pthread_mutexattr_t 类型变量,以方便我们获取和设置互斥锁属性。以下是一些主要的函数:

#include <pthread.h>
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

互斥锁两种常用属性:pshared 和 type。

互斥锁属性pshared指定是否允许跨进程共享互斥锁,其可选值有两个:

  • PTHREAD_PROCESS_SHARED。互斥锁可以被跨进程共享
  • PTHREAD_PROCESS_PRIVATE。互斥锁只能被和锁的初始化线程隶属于同一个进程的线程共享。

互斥锁属性type指定互斥锁的类型。Linux支持如下4种类型的互斥锁:

  • PTHREAD_MUTEX_NORMAL,普通锁。这是互斥锁默认的类型。当一个线程对一个普通锁加锁以后,其余请求该所的线程将形成一个等待队列,并在该所解锁后按优先级获得它。这种锁类型保证了资源分配的公平性。但这种锁也很容易引发问题:一个线程如果对一个已经加锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁解锁将导致不可预期的后果。
  • PTHREAD_MUTEX_ERRORCHECK,检错锁。一个线程如果对一个已经加锁的检错锁再次加锁,则加锁操作返回EDEADLK。对一个已经被其让他线程加锁的检错锁解锁,或者对一个已经解锁的检错锁再次解锁,则检错锁返回EPERM。
  • PTHREAM_MUTEX_RECURSIVE,嵌套锁。这种锁允许一个线程在释放锁之前对他加锁而不发生死锁。不过其他线程如果要获得这个锁,则当前锁的拥有者必须执行相应次数的解锁操作。对一个已经被其他线程枷锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM。
  • PTHREAD_MUTEX_DEFAULT,默认锁。一个线程如果对一个已经加锁的默认锁再次加锁,或者对一个已经被其他线程加锁的默认锁解锁,或者对一个已经解锁的默认锁再次解锁,将导致不可预期的后果。

3、条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制。

主要包括两个动作:

  • 一个线程等待 “条件变量的条件成立” 而挂起;
  • 另一个线程使 ”条件成立”(给出条件成立信号);

条件变量相关函数:

#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cv,const pthread_condattr_t *cattr);
// 等待条件变量
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
// 等待条件变量到指定时间
int pthread_cond_timedwait(pthread_cond_t *cv,pthread_mutex_t *mp, const structtimespec * abstime);
// 通知等待条件变量的单个线程
int pthread_cond_signal(pthread_cond_t *cv);
// 通知等待条件变量的所有线程
int pthread_cond_broadcast(pthread_cond_t *cv);
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cv);

4、条件变量和互斥锁

mutex 体现的是一种竞争,我离开了,通知你进来。

cond 体现的是一种协作,我准备好了,通知你开始吧。

互斥锁一个明显的缺点是它只有两种状态锁定非锁定。而条件变量通过允许线程阻塞等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。

四、线程池

在这里插入图片描述
线程池内部实际上构建了一个生产者消费者模型,将线程任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分任务管理(Master)线程管理(Worker)

1、任务封装

Worker主要作用是执行任务,因此需要对任务进行封装,以便提供一个统一的接口。任务封装在不同场景中封装不同,常用格式如下:

typedef struct{
	void (*func)(void *arg);     //任务函数
	void *user_data;			 //函数参数
}task_t;

2、任务队列

任务缓存是队列进行存放。

3、Master 功能

1、线程池管理。

2、任务队列管理。

3、任务管理(任务队列有空闲缓存,无空闲拒绝)。

4、Worker 功能

1、无任务时阻塞;

2、有任务时执行;

5、设计

本章节主要说明程序设计思路

1、任务封装

本示例只测试线程池功能,因此只实现简单功能,任务封装设计如下:

typedef struct{
	void (*func)(void *arg);     //任务函数
	void *user_data;			 //函数参数
}task_t;

2、worker 线程设计

1、核心线程池、最大线程数指定。核心线程池和最大线程数在不同环境中设计有所差异,因此,需要参数可配置。

#define CORE_POOL_SIZE  5
#define MAX_POOL_SIZE   20

2、核心线程池创建。线程创建以后,因为线程目前没有任务,因此线程池中线程不能参数调度(不能占用系统资源)。所以线程创建以后需要进入阻塞睡眠状态(通过 pthread_cond_t 完成)。

3、master 线程设计

管理线程创建。管理线程负责如下功能:

  • 线程池管理。
  • 任务队列管理。
  • 任务管理(任务队列有空闲缓存,无空闲拒绝)。

特别说明:根据不同要求,实现有所不同。

4、任务队列设计

任务具有先进先出的特点,因此使用队列进行任务缓存,且任务缓存最大数量应该是可配置的。

5、任务管理设计

任务管理设计,任务数量和线程池线程数量分以下几种情况:

  • 任务数量小于等于空闲线程数量,任务不需要缓存
  • 任务数量大于空闲线程数量,任务需要缓存
  • 任务数量大于缓存容量和线程池数量,拒绝接收任务。

6、任务分配机制设计

任务分配主要是管理线程接收到任务后,将任务分配线程池中线程

设计难点:

  • 线程池中线程处于阻塞睡眠状态线程唤醒功能只有操作系统可以完成,因此管理线程无法唤醒指定线程

设计思路:

  • 管理线程无法唤醒指定线程,因此管理线程发送唤醒信号,由操作系统完成唤醒工作。
  • 唤醒工作线程前将任务进行缓存,工作线程唤醒后提取任务并执行。
  • 任务缓存区可能被管理线程和工作线程同时访问,因此需要互斥访问

8、异常处理

异常情况根据具体设计有所不同,常见异常情况如下:

  • 线程资源回收问题。
  • 共享资源需要互斥访问。
  • 避免死锁。
  • ……

6、实现

1、Makefile

# 版本信息:2022.04.02 - lqonlylove-v1.0.0



##############################
# 目标
##############################
TARGET := threadPool




##############################
# 环境参数
##############################
# CURDIR - make 自动指定(不用修改)
# SHELL  - make 自动指定(不用修改)




##############################
# 编译参数
##############################
CC      := g++
#LD		:=
LDLIBS  :=
LDFLAGS := -lpthread
DEFINES :=
#INCLUDE := -I.
CFLAGS 	= -g -Wall -O3 $(DEFINES) $(INCLUDE)
CXXFLAGS = $(CFLAGS) -DHAVE_CONFIG_H
#LOADLIBES := 
#OUTPUT_OPTION := 

#OBJCOPY :=
#OBJDUMP :=




##############################
# 目录管理
##############################
# 设置编译目录
BUILD_PATH := build
# 编译过程文件存放目录
OBJ_PATH := $(BUILD_PATH)/temp
# 编译可执行文件存放目录
BIN_PATH := $(BUILD_PATH)/bin

# 头文件目录(根据需要调整)
INC_PATH := user \
			threadPool

# 源文件目录(根据需要调整)
SRC_PATH := user \
			threadPool

# 获取源文件下的 c 文件列表(带目录信息)
SRC := $(foreach dir,$(SRC_PATH),$(wildcard $(dir)/*.cpp))
SRC_WITHOUT_DIR := $(notdir $(SRC))

# 为头文件目录添加 -I 选项(详细内容见 gcc 使用手册)
INCLUDE = $(addprefix -I ,$(INC_PATH))

# 生成 .c 文件对应的 .o 文件列表
OBJ := $(patsubst %.cpp,%.o,$(SRC_WITHOUT_DIR))
# 为 .o 加上编译目录
OBJ_WITH_BUILD_DIR := $(addprefix $(OBJ_PATH)/,$(OBJ))

# 指定 makefile 源文件查找路径(非常重要)
VPATH := $(SRC_PATH)






##############################
# makefile 目标管理
##############################
all: build_path $(TARGET)

# 指定目标文件生成规则
$(TARGET):$(OBJ_WITH_BUILD_DIR)
	$(CC) -o $(BIN_PATH)/$@ $^ $(LDFLAGS)

# 指定过程文件生成规则(详细内容见 makefile 静态模式)
$(OBJ_WITH_BUILD_DIR):$(OBJ_PATH)/%.o: %.cpp
	$(CC) -c $(CFLAGS) -o $@ $<

build_path:
	@if [ ! -d $(BUILD_PATH) ]; then \
  	mkdir -p $(OBJ_PATH);mkdir -p $(BIN_PATH);\
	fi


# 清除过程文件
.PHONY: clean print
# 打印部分信息(测试使用)
print:
	@echo $(BIN_PATH)

clean:
	-rm -rf $(BIN_PATH)/$(TARGET) $(OBJ_WITH_BUILD_DIR)

2、task.h

#pragma once

typedef struct{
	void (*func)(void *arg);     //任务函数
	void *user_data;			 //函数参数
}task_t;

3、threadPool.h

#pragma once
#include "task.h"
#include "pthread.h"
#include <queue>

#define CORE_POOL_SIZE  5
#define MAX_POOL_SIZE   20

class threadPool{
    friend void* worker(void* arg);
    friend void* manager(void* arg);
public:
    threadPool(unsigned int minPoolSize,unsigned int maxPoolSize,unsigned int adjustSize);
    ~threadPool();
    void addTask(task_t task);
private:
    task_t takeTask(void);
    void threadExit(void);
private:
    /************ 管理者线程相关资源 ***************/
    pthread_t managerThreadId;          // 管理者线程ID
    /************ 线程池相关资源 ***************/
    pthread_t* workThreadId = nullptr;  // 工作线程ID
    unsigned int workAliveNum = 0;      // 活着的线程数(有可能多个线程同时访问,属于临界资源)
    unsigned int workBusyNum = 0;       // 工作线程数(有可能多个线程同时访问,属于临界资源)
    unsigned int workExitNum = 0;       // 动态销毁线程数
    pthread_mutex_t workMutex;          // 线程池临界资源包含
    /************ 公共资源 ***************/
    bool shutdown = false;              // 线程池状态(shutdown==true:线程池关闭)
    pthread_cond_t taskCond;            // 管理线程和工作线程同步机制
    unsigned int minPoolNum = 0;        // 线程池最小程数
    unsigned int maxPoolNum = 0;        // 线程池最大线程数
    unsigned int adjustNum = 0;         // 线程池动态调整数
    /************ 任务队列资源 ***************/
    std::queue<task_t> task;            // 任务队列
    pthread_mutex_t taskMutex;          // 任务队列互斥机制
};

4、threadPool.c

#include "threadPool.h"
#include <cstring>
#include <unistd.h>
#include <iostream>
#include <string>

// 线程函数不可以作为某个类的成员函数
void* worker(void* arg)
{
    threadPool *pool = static_cast<threadPool*>(arg);
    task_t task = {};
    // 设置线程分离状态,避免僵尸线程产生
    pthread_detach(pthread_self());
    // 工作线程应该处于不断处理任务状态
    while(1){
        // 查询是否有任务,需要加锁访问共享资源
        pthread_mutex_lock(&pool->taskMutex);
        // 判断是否有任务,无任务释放锁并进入休眠阻塞状态(释放系统资源并不参与调度)
        // 逻辑上应该使用if,但需要处理假唤醒情况,因此唤醒以后因该再次确定是否有任务
        while(pool->task.size()==0 && pool->shutdown!=true){
            pthread_cond_wait(&pool->taskCond,&pool->taskMutex);
            // 判断是否需要销毁线程
            if(pool->workExitNum>0){
                pool->workExitNum--;
                if(pool->workAliveNum > pool->minPoolNum){
                    // 退出当前线程
                    pool->workAliveNum--;
                    pthread_mutex_unlock(&pool->taskMutex);
                    pool->threadExit();
                }else{
                    // 不做任何处理
                }
            }
        }
        // 判断线程池是否被关闭
        if(pool->shutdown==true){
            // 释放锁
            pthread_mutex_unlock(&pool->taskMutex);
            // 退出线程
            pool->threadExit();
        }
        // 提取任务
        task = pool->takeTask();
        // 解锁线程池
        pthread_mutex_unlock(&pool->taskMutex);
        // 执行任务
        pthread_mutex_lock(&pool->workMutex);
        pool->workBusyNum++;
        pthread_mutex_unlock(&pool->workMutex);
        std::cout << "worker thread " << std::to_string(pthread_self()) << " start working..." <<std:: endl;
        task.func(task.user_data);
        std::cout << "worker thread " << std::to_string(pthread_self()) << " end working..." <<std:: endl;
        pthread_mutex_lock(&pool->workMutex);
        pool->workBusyNum--;
        pthread_mutex_unlock(&pool->workMutex);
    }
    
    return nullptr;
}

// 线程函数不可以作为某个类的成员函数
void* manager(void* arg)
{
    threadPool *pool = static_cast<threadPool*>(arg);
    unsigned int liveNum = 0;
    unsigned int busyNum = 0;
    unsigned int queueSize = 0;
    unsigned int num = 0;
    // 线程池未关闭,需要一直检测
    std::cout << "manager thread " << std::to_string(pthread_self()) << " start working..." <<std:: endl;
    while(pool->shutdown!=true){
        // 检测间隔(应该设计为用户可配置)
        sleep(5);
        /************ 动态管理线程 ************/
        // 获取当前存活线程数和空闲线程数
        pthread_mutex_lock(&pool->workMutex);
        liveNum = pool->workAliveNum;
        busyNum = pool->workBusyNum;
        pthread_mutex_unlock(&pool->workMutex);
        // 获取任务队列缓存任务个数
        pthread_mutex_lock(&pool->taskMutex);
        queueSize = pool->task.size();
        pthread_mutex_unlock(&pool->taskMutex);
        // 动态调整work线程数
        // 当前任务个数>存活的线程数 && 存活的线程数<最大线程个数,添加线程
        if (queueSize > liveNum && liveNum < pool->maxPoolNum){
            num = 0;
            pthread_mutex_lock(&pool->workMutex);
            // 或者线程 < 最大线程 && 创建线程
            for(unsigned int i=0;pool->workAliveNum<pool->maxPoolNum && num<pool->adjustNum;i++){
                if (pool->workThreadId[i] == 0){
                    pthread_create(&pool->workThreadId[i],NULL,worker,pool);
                    num++;
                    pool->workAliveNum++;
                }
            }
            pthread_mutex_unlock(&pool->workMutex);
        }
        // 忙线程*2 < 存活的线程数目 && 存活的线程数 > 最小线程数量,销毁线程
        if(busyNum * 2 < liveNum && liveNum > pool->minPoolNum){
            pthread_mutex_lock(&pool->workMutex);
            pool->workExitNum = pool->adjustNum;
            pthread_mutex_unlock(&pool->workMutex);
            for(unsigned int i=0;i<pool->workExitNum;i++){
                pthread_cond_signal(&pool->taskCond);
            }
        }
    }
    std::cout << "manager thread " << std::to_string(pthread_self()) << " end working..." <<std:: endl;
    return nullptr;
}

threadPool::threadPool(unsigned int minPoolSize,unsigned int maxPoolSize,unsigned int adjustSize)
{
    minPoolNum = minPoolSize;
    maxPoolNum = maxPoolSize;
    adjustNum = adjustSize;
    workAliveNum = minPoolNum;
    // 创建工作线程ID保存数组
    workThreadId = new pthread_t[maxPoolNum];
    if(workThreadId == nullptr){
        goto WORK_THREAD_ID_ERROE;
    }
    memset(workThreadId, 0, sizeof(pthread_t) * maxPoolNum);
    // 任务队列初始化锁和条件变量
    if (pthread_mutex_init(&taskMutex, NULL)!=0 ||pthread_cond_init(&taskCond, NULL) != 0){
        goto TASK_MUTEX_COND_ERROR;
    }
    // 线程池初始化锁和条件变量
    if (pthread_mutex_init(&workMutex, NULL)!=0){
        goto WORK_MUTEX_COND_ERROR;
    }
    // 创建核心线程
    for(unsigned int i=0;i<minPoolNum;i++){
        pthread_create(&workThreadId[i],NULL,worker,this);
    }
    // 创建管理者线程
    pthread_create(&managerThreadId,NULL,manager,this);
    return;

WORK_MUTEX_COND_ERROR:
    pthread_mutex_destroy(&taskMutex);
    pthread_cond_destroy(&taskCond);
TASK_MUTEX_COND_ERROR:
    delete [] workThreadId;
WORK_THREAD_ID_ERROE:
    return;
}

threadPool::~threadPool()
{
    // 释放申请的资源
    shutdown = true;
    // 唤醒所有消费者线程
    for (unsigned int i = 0; i < workAliveNum; ++i){
        pthread_cond_signal(&taskCond);
    }
    // 销毁管理者线程
    pthread_join(managerThreadId,NULL);
    delete [] workThreadId;
    pthread_mutex_destroy(&taskMutex);
    pthread_cond_destroy(&taskCond);
}

void threadPool::addTask(task_t taskTemp)
{
    // 如果线程池关闭,不可以添加任务
    if(shutdown==true){
        return;
    }
    // 向任务队列添加任务
    pthread_mutex_lock(&taskMutex);
    task.push(taskTemp);
    pthread_mutex_unlock(&taskMutex);
    // 唤醒工作线程
    pthread_cond_signal(&taskCond);
}

task_t threadPool::takeTask(void)
{
    // 不使用master线程进行管理,直接向任务队列添加任务
    task_t taskTemp = {};
    taskTemp = task.front();
    task.pop();
    return taskTemp;
}

void threadPool::threadExit(void)
{
    pthread_t tid = pthread_self();
    for (unsigned int i = 0; i < maxPoolNum; i++){
        if(workThreadId[i]==tid){
            std::cout << "threadExit() function: thread " << std::to_string(pthread_self()) << " exiting..." <<std:: endl;
            workThreadId[i] = 0;
            break;
        }
    }
    pthread_exit(NULL);
}

5、main.c

#include <queue>
#include <string>
#include <iostream>
#include "task.h"
#include "threadPool.h"
#include <unistd.h>

void func(void *arg)
{
    std::cout << "func " << std::to_string(pthread_self()) << " working……" <<std:: endl;
}

int main(int argc, char* argv[])
{
    task_t task;
    task.func = func;
    task.user_data = NULL;
    // 创建线程池(最小线程:500;最大线程:2000;动态调整:20)
    threadPool pool(500,2000,20);
    for(unsigned int i=0;i<1500;i++){
        pool.addTask(task);
    }
    sleep(10);
    return 0;
}

7、测试

onlylove@ubuntu:~/my/linux/threadPool$ tree .
.
├── Makefile
├── threadPool
│   ├── task.h
│   ├── threadPool.cpp
│   └── threadPool.h
└── user
    └── main.cpp

2 directories, 5 files
onlylove@ubuntu:~/my/linux/threadPool$ make
g++ -c -g -Wall -O3  -I user -I threadPool -o build/temp/main.o user/main.cpp
g++ -c -g -Wall -O3  -I user -I threadPool -o build/temp/threadPool.o threadPool/threadPool.cpp
g++ -o build/bin/threadPool build/temp/main.o build/temp/threadPool.o -lpthread
onlylove@ubuntu:~/my/linux/threadPool$ tree .
.
├── build
│   ├── bin
│   │   └── threadPool
│   └── temp
│       ├── main.o
│       └── threadPool.o
├── Makefile
├── threadPool
│   ├── task.h
│   ├── threadPool.cpp
│   └── threadPool.h
└── user
    └── main.cpp

5 directories, 8 files
onlylove@ubuntu:~/my/linux/threadPool$
onlylove@ubuntu:~/my/linux/threadPool$ ./build/bin/threadPool
manager thread worker thread worker thread worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...
func 139934542825216 working……
worker thread 139934542825216 end working...
worker thread 139934542825216 start working...

……

threadExit() function: thread 139933040531200 exiting...
threadExit() function: thread 139934500861696 exiting...
139933611235072 exiting...
threadExit() function: thread 139933074102016 exiting...
threadExit() function: thread 139930866820864 exiting...
threadExit() function: thread 139932427863808 exiting...
threadExit() function: thread 139931941086976 exiting...
threadExit() function: thread 139933292312320 exiting...
threadExit() function: thread 139930413614848 exiting...
139931571808000 exiting...
threadExit() function: thread 139931093423872 exiting...
threadExit() function: thread 139931680913152 exiting...
threadExit() function: thread 139930430400256 exiting...
 exiting...
manager thread 139930346473216 end working...

特别说明:未进行详细测试。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值