[多线程] C++ 手写线程池的设计

记录一下项目中使用的线程池。
因为当时项目规模不大,所以写的比较随意,这里整理一下线程池的知识。

为什么用多线程

  1. 避免阻塞。
    在单线程编程中,往往会调用一个函数,然后等待函数返回再继续执行。
    如果这个函数特别耗时间呢?如果需要处理很多任务呢,跟这个函数无关的任务处理也需要等待它。
    使用多线程可以有效避免阻塞。
  2. 并发。
    同步的单线程是无法进行很耗时的并发操作的。比如多个客户端同时从服务端下载文件,服务端不能在一个循环中,一个一个发送吧,发送完一个客户端的再发送下一个客户端的。服务端可以为每一个客户端分配一个线程,进行文件传输(假设)。这样就比单线程的处理要快很多了。

为什么使用线程池

在工作中,经常会使用多线程。但是多线程处理少量的并发任务还可以,如果对应比较多的并发连接,不应该每个连接分配一个线程吗。如果有一百个连接就创建一百个线程,五百个连接就创建五百个线程,这明显不是个好方法。

首先由于内存的限制,进程能申请的线程数量终归是有限制的。
其次,线程数量太多,系统在线程间切换,本身也是一种负担。

所以就有了线程池这个组件。
线程池中维护这一定数量的线程,通过这些线程的复用来实现提高效率等目的。

设计一个简单的线程池

线程池的结构

线程池是一个生产者消费者模型,生产者负责产生任务,消费者负责处理任务,中间有个任务队列用于缓存任务。
很多项目中,都有线程池,无论是什么线程池,基本的结构是差不多的。

  1. 生产者:发布任务的模块
  2. 消费者:管理多线程的模块
  3. 任务队列:用于存储等待处理的任务
  4. 条件变量和unique_lock:用于控制线程的阻塞和执行
    在这篇文章中,生产者就是主线程,No2、3、4构成了线程池模块。

线程的数量

线程池的参数之一,可以分为:核心线程数和最大线程数。
每个项目的线程数设定可能都不一样,要根据需求和测试结果来调整。
我的项目比较简单,只设置了最大线程数,最大线程数是[2* cpu核心数 + 1]。
widows中可以通过以下代码获取cpu核心数量:

//获取cpu内核数量
	int icore_num = std::thread::hardware_concurrency();
	if (icore_num > 0)
		m_max_thread_count = 2 * icore_num;
	else
		m_max_thread_count = 4;

线程池的基本逻辑

当有任务交给线程池,首先任务会被放到任务队列中。
如果此时线程池没有空闲线程,且当前线程数量小于最大线程数,则会创建一个线程,去任务队列中取任务。
如果此时线程池没有空闲线程,且当前线程数量等于最大线程数,则任务队列中的任务需要等待。

void ThreadPoolMgr::AddTask(Task& t)
{
	//当前线程数量小于最大线程数量,并且 空闲线程的计数不为0
	if (m_cur_thread_count < m_max_thread_count && m_idle_thread_count == 0)
	{
		{//简陋的并发输出log
			std::lock_guard<std::mutex> prn_lkg(print_mtx);
			cout << "AddTask, m_cur_thread_count:" << m_cur_thread_count <<
				" m_max_thread_count:" << m_max_thread_count <<
				" m_idle_thread_count:" << m_idle_thread_count << endl;
		}
		//创建线程
		m_thread_list.emplace_back(std::bind( &ThreadPoolMgr::Thread_loop_func, this));
			//更新当前线程数数量
		++m_cur_thread_count;  //这是个原子变量
	}
	Add2TaskQueue(t);//把task添加到任务队列
}

线程池中有一个单独的监视线程,负责不断检测任务队列和空闲线程。
当任务队列有缓存任务,并且也有空闲线程,就会发布一个通知,通知阻塞的空闲线程来取任务。

//监视线程
void ThreadPoolMgr::Thread_Check_func()
{
	{ //简陋的并发输出log
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_Check_func in, thread;" << std::this_thread::get_id() << endl;
	};
	while(1) 
	{
		if (m_idle_thread_count && GetTaskqueueSize())
		{
			{
				std::lock_guard<std::mutex> prn_lkg(print_mtx);
				cout << "Thread:Thread_Check_func notify, m_idle_thread_count;" << m_idle_thread_count << endl;
			};
			//当空闲线程计数不为0,且任务队列中有缓存任务,发动条件变量通知
			m__taskque_cdn.notify_one();
		}
		else
		{
		    //当线程池结束标志位true,并且任务队列中没有任务,退出线程		 
			if (m_bIsQuit && GetTaskqueueSize() == 0)
				break;
		}
		Sleep(500);
	}
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_Check_func out, thread;" << std::this_thread::get_id() << endl;
	};
}

如何实现线程的复用

我使用C++11写的代码,在C++中没有封装线程池这样的组件,只能手写。
往往我们使用多线程,就是创建一个线程对象,传入一个函数,函数执行完了,线程也就结束了,
如何实现函数执行完了,还能保留线程呢?

办法就是:让线程执行一个循环从任务队列获取task的函数,task中有一个函数指针,通过函数指针回调task的任务处理函数。
这样在执行完回调的任务函数后,仍然在函数循环中,起到了保留线程的目的。

//线程执行函数
void ThreadPoolMgr::Thread_loop_func()
{
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_loop_func in, thread NO."<< m_cur_thread_count << " ID:" << std::this_thread::get_id() << endl;
	};
	
	Task* pt = new Task();	 //新建一个task对象
	while (!m_bIsQuit)
	{
		++m_idle_thread_count; //当线程阻塞时,空闲线程计数+1
		if (!GetTaskfromQueue(pt))//从队列总获取task
		{
			--m_idle_thread_count;
			continue;
		}
		--m_idle_thread_count; //当线程执行task时,空闲线程计数-1
		pt->m_func_ptr(pt->m_arg_struct, pt->m_arg_size); //执行task中的回调函数
	}
	delete pt;

	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_loop_func out, thread;" << std::this_thread::get_id() << endl;
	};

	m_cur_thread_count--;
}

如何启动线程和阻塞线程

这里需要使用C++中的条件变量(condition_variable)和互斥量(mutex)。

//线程从任务队列获取task的函数
bool ThreadPoolMgr::GetTaskfromQueue(Task* pt) 
{
	std::unique_lock<std::mutex> ulk(m_taskque_mtx); //互斥量加锁
	m__taskque_cdn.wait(ulk);//互斥量自动解锁,同时环境变量等待
	//环境变量收到通知。互斥量自动加锁
	if (!m_task_queue.size())
		return false; 
		
	*pt = m_task_queue.front(); //获取队列头的task
	m_task_queue.pop(); //移除队列头的task
	ulk.unlock(); //对队列的访问完成,互斥量解锁
	return true;
}

如何结束线程池,释放线程

要结束线程池的时候,不能强制杀死线程。
因为线程中可能有申请的堆空间等资源,需要线程释放。
可能还有的线程正在执行任务。

所以结束线程池时,会自动循环检索几个条件:
1.任务队列空了
2.所有线程都执行完任务了
此时修改线程函数中的循环条件,然后环境变量通知所有线程,使所有线程接触阻塞,
检查循环条件,解除循环,释放资源,退出线程。

void ThreadPoolMgr::Finish()
{
	
	while (1)
	{
		if(GetTaskqueueSize()) //确保任务队列已经没有任务
		{
			continue;
		}
		if (m_idle_thread_count != m_cur_thread_count) //确保所有任务都空闲
		{
			continue;
		}
		m_bIsQuit = true; //更改线程函数循环条件
		m__taskque_cdn.notify_all();
		if (m_cur_thread_count)//确保所有线程函数都退出
		{
			continue;
		}
		else
			break;
		Sleep(1000);
	}
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Threadpool all task finished and threads quit." << endl;
	}
	Join();
	if(!m_thread_list.empty())
		m_thread_list.clear();
}

线程池代码

主线程:获取线程池对象,添加任务

#include <iostream>
#include <string>
#include <mutex>
#include "ThreadPool.h"

extern std::mutex print_mtx;

struct Arg
{
    int task_num;
    char buff[256] = { 0x00 };
};

void task_func(void* arg, int size)
{
    Arg* parg = (Arg*)arg;
    std::lock_guard<std::mutex> lkg(print_mtx);
    std::cout << "Num:" << parg->task_num
        << "," << parg->buff << "(" << std::this_thread::get_id() << ")" << std::endl;
}

int main()
{
    ThreadPoolMgr *pthsMgr = ThreadPoolMgr::GetThreadPoolInstance();
    pthsMgr->Init();

    for (int i = 0; i < 100; i++)
    {
        Arg arg;
        arg.task_num = i;     
        sprintf_s(arg.buff, 256, "%s%d", "This is task num:" , arg.task_num);
        
        Task task;
        task.m_arg_struct = &arg;
        task.m_arg_size = sizeof(arg); 
        task.m_func_ptr = task_func;
        pthsMgr->AddTask(task);
    }

    pthsMgr->Finish();

}

线程池头文件

#pragma once
#include <queue>
#include <mutex>
#include <vector>
#include <thread>
#include <future>
#include <atomic>
#include <condition_variable>


//任务结构体
using Task_Callback_func = std::function<void(void*,int)>;
struct Task
{
public:
	Task() { 
		m_func_ptr = nullptr; 
		m_arg_struct = nullptr; 
		m_arg_size = 0;
	}
	Task(const Task& t) {
		m_func_ptr = t.m_func_ptr;
		m_arg_size = t.m_arg_size;
		if (m_arg_size > 0)
		{
			m_arg_struct = new char[m_arg_size];
			memcpy_s(m_arg_struct, m_arg_size,
				t.m_arg_struct, t.m_arg_size);
		}
	}
public:
	Task_Callback_func m_func_ptr;
	void*  m_arg_struct;
	int	  m_arg_size;
};

struct ThreadPoolMgr
{
private:	
	ThreadPoolMgr() :m_bIsQuit(false), m_max_thread_count(1), m_check_ptr(nullptr) { } 
	ThreadPoolMgr(const ThreadPoolMgr&) = delete;
	ThreadPoolMgr& operator=(const ThreadPoolMgr&) = delete;
public:
	~ThreadPoolMgr() {
		if (m_check_ptr)
			delete m_check_ptr;
	}
public:
	//单例-饿汉模式创建线程池对象,保证全局唯一
	static ThreadPoolMgr* GetThreadPoolInstance()
	{
		static ThreadPoolMgr thMgr;
		return &thMgr;
	}
public: //公开的接口
	void Init();
	void Finish();
	void AddTask(Task&);
private:
	//任务队列
	std::mutex m_taskque_mtx;
	std::condition_variable m__taskque_cdn;	
	std::queue<Task> m_task_queue;

	//线程队列
	std::vector<std::thread> m_thread_list;
	int m_max_thread_count;
	std::atomic<int> m_idle_thread_count;
	std::atomic<int> m_cur_thread_count;

	//监视线程
	std::thread* m_check_ptr;

private:
	void Join();
private:
	void Add2TaskQueue(Task&);
	bool GetTaskfromQueue(Task*);
	int GetTaskqueueSize();
private:
	void Thread_loop_func();	
private:
	bool m_bIsQuit;
	void Thread_Check_func();
};

线程池cpp

#include "ThreadPool.h"
#include <iostream>
#include <windows.h>

using std::cout;
using std::endl;

std::mutex print_mtx;

void ThreadPoolMgr::Init()
{
	//清空队列
	while (!m_task_queue.empty())
		m_task_queue.pop();
	m_thread_list.clear();

	//获取cpu内核数量
	int icore_num = std::thread::hardware_concurrency();
	if (icore_num > 0)
		m_max_thread_count = 2 * icore_num;
	else
		m_max_thread_count = 4;
	m_idle_thread_count = 0;
	m_cur_thread_count = 0;

	m_thread_list.reserve(icore_num);
	m_bIsQuit = false;

	m_check_ptr = new std::thread(std::bind(&ThreadPoolMgr::Thread_Check_func, this));
	return;
}

void ThreadPoolMgr::Finish()
{
	
	while (1)
	{
		if(GetTaskqueueSize()) //确保任务队列已经没有任务
		{
			continue;
		}
		if (m_idle_thread_count != m_cur_thread_count) //确保所有任务都空闲
		{
			continue;
		}
		m_bIsQuit = true; //更改线程函数循环条件
		m__taskque_cdn.notify_all();
		if (m_cur_thread_count)//确保所有线程函数都退出
		{
			continue;
		}
		else
			break;
		Sleep(1000);
	}
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Threadpool all task finished and threads quit." << endl;
	}
	Join();
	if(!m_thread_list.empty())
		m_thread_list.clear();
}

void ThreadPoolMgr::AddTask(Task& t)
{
	if (m_cur_thread_count < m_max_thread_count 
		&& m_idle_thread_count == 0)
	{
		{
			std::lock_guard<std::mutex> prn_lkg(print_mtx);
			cout << "AddTask, m_cur_thread_count:" << m_cur_thread_count <<
				" m_max_thread_count:" << m_max_thread_count <<
				" m_idle_thread_count:" << m_idle_thread_count << endl;
		}
		m_thread_list.emplace_back(std::bind( &ThreadPoolMgr::Thread_loop_func, this));
		++m_cur_thread_count;
	}
	Add2TaskQueue(t);
}

void ThreadPoolMgr::Join()
{
	int threads_size = m_thread_list.size();
	for (int i = 0; i < threads_size; i++)
	{
		if (m_thread_list[i].joinable())
			m_thread_list[i].join();
	}
	if (m_check_ptr)
		m_check_ptr->join();
	return;
}

void ThreadPoolMgr::Add2TaskQueue(Task& t)
{
	std::lock_guard<std::mutex> lg(m_taskque_mtx);
	m_task_queue.push(t);
	m__taskque_cdn.notify_one();
	return;
}
bool ThreadPoolMgr::GetTaskfromQueue(Task* pt) 
{
	std::unique_lock<std::mutex> ulk(m_taskque_mtx);
	m__taskque_cdn.wait(ulk);

	if (!m_task_queue.size())
		return false; 
		
	*pt = m_task_queue.front();
	m_task_queue.pop();
	ulk.unlock();
	return true;
}
int ThreadPoolMgr::GetTaskqueueSize()
{
	std::lock_guard<std::mutex> lg(m_taskque_mtx);
	return m_task_queue.size();
}

void ThreadPoolMgr::Thread_loop_func()
{
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_loop_func in, thread NO."<< m_cur_thread_count << " ID:" << std::this_thread::get_id() << endl;
	};
	
	Task* pt = new Task();	
	while (!m_bIsQuit)
	{
		++m_idle_thread_count;
		if (!GetTaskfromQueue(pt))
		{
			--m_idle_thread_count;
			continue;
		}
		--m_idle_thread_count;
		pt->m_func_ptr(pt->m_arg_struct, pt->m_arg_size);
	}
	delete pt;

	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_loop_func out, thread;" << std::this_thread::get_id() << endl;
	};

	m_cur_thread_count--;
}

void ThreadPoolMgr::Thread_Check_func()
{
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_Check_func in, thread;" << std::this_thread::get_id() << endl;
	};
	while(1) 
	{
		if (m_idle_thread_count && GetTaskqueueSize())
		{
			{
				std::lock_guard<std::mutex> prn_lkg(print_mtx);
				cout << "Thread:Thread_Check_func notify, m_idle_thread_count;" << m_idle_thread_count << endl;
			};

			m__taskque_cdn.notify_one();
		}
		else
		{
			if (m_bIsQuit && GetTaskqueueSize() == 0)
				break;
		}
		Sleep(500);
	}
	{
		std::lock_guard<std::mutex> prn_lkg(print_mtx);
		cout << "Thread:Thread_Check_func out, thread;" << std::this_thread::get_id() << endl;
	};
}

执行结果:

AddTask, m_cur_thread_count:0 m_max_thread_count:24 m_idle_thread_count:0
AddTask, m_cur_thread_count:1 m_max_thread_count:24 m_idle_thread_count:0
AddTask, m_cur_thread_count:2 m_max_thread_count:24 m_idle_thread_count:0
AddTask, m_cur_thread_count:3 m_max_thread_count:24 m_idle_thread_count:0
AddTask, m_cur_thread_count:4 m_max_thread_count:24 m_idle_thread_count:0
Thread:Thread_Check_func in, thread;25800
Thread:Thread_loop_func in, thread NO.5 ID:18564
Thread:Thread_loop_func in, thread NO.5 ID:28596
Thread:Thread_loop_func in, thread NO.5 ID:8816
Thread:Thread_loop_func in, thread NO.5 ID:3604
AddTask, m_cur_thread_count:5 m_max_thread_count:24 m_idle_thread_count:4
Thread:Thread_loop_func in, thread NO.5 ID:28608
Num:0,This is task num:0(18564)
Num:1,This is task num:1(28596)
Num:2,This is task num:2(8816)
Num:3,This is task num:3(3604)
Thread:Thread_loop_func in, thread NO.6 ID:21224
AddTask, m_cur_thread_count:6 m_max_thread_count:24 m_idle_thread_count:6
Num:4,This is task num:4(28608)
Num:5,This is task num:5(18564)
Num:6,This is task num:6(28596)
Num:7,This is task num:7(8816)
Num:8,This is task num:8(3604)
Thread:Thread_loop_func in, thread NO.7 ID:16868
Num:9,This is task num:9(21224)
AddTask, m_cur_thread_count:7 m_max_thread_count:24 m_idle_thread_count:7
Num:10,This is task num:10(28608)
Num:11,This is task num:11(18564)
Num:12,This is task num:12(28596)
Num:13,This is task num:13(8816)
Num:14,This is task num:14(3604)
Thread:Thread_loop_func in, thread NO.8 ID:14256
Num:15,This is task num:15(16868)
Num:16,This is task num:16(21224)
AddTask, m_cur_thread_count:8 m_max_thread_count:24 m_idle_thread_count:8
Num:17,This is task num:17(28608)
Num:18,This is task num:18(18564)
Num:19,This is task num:19(28596)
Num:20,This is task num:20(8816)
Num:21,This is task num:21(3604)
Thread:Thread_loop_func in, thread NO.9 ID:23096
Num:22,This is task num:22(16868)
Num:23,This is task num:23(14256)
Num:24,This is task num:24(21224)
AddTask, m_cur_thread_count:9 m_max_thread_count:24 m_idle_thread_count:9
Num:25,This is task num:25(28608)
Num:26,This is task num:26(18564)
Num:27,This is task num:27(28596)
Num:28,This is task num:28(8816)
Num:29,This is task num:29(3604)
Num:30,This is task num:30(23096)
Thread:Thread_loop_func in, thread NO.10 ID:4776
Num:31,This is task num:31(16868)
Num:32,This is task num:32(14256)
Num:33,This is task num:33(21224)
AddTask, m_cur_thread_count:10 m_max_thread_count:24 m_idle_thread_count:10
Num:34,This is task num:34(28608)
Num:35,This is task num:35(18564)
Num:36,This is task num:36(28596)
Num:37,This is task num:37(8816)
Num:38,This is task num:38(3604)
Num:39,This is task num:39(23096)
Thread:Thread_loop_func in, thread NO.11 ID:29204
Num:40,This is task num:40(4776)
Num:41,This is task num:41(16868)
Num:42,This is task num:42(21224)
Num:43,This is task num:43(14256)
AddTask, m_cur_thread_count:11 m_max_thread_count:24 m_idle_thread_count:11
Num:44,This is task num:44(28608)
Num:45,This is task num:45(18564)
Num:46,This is task num:46(28596)
Num:47,This is task num:47(8816)
Thread:Thread_loop_func in, thread NO.12 ID:4948
Num:48,This is task num:48(3604)
Num:49,This is task num:49(23096)
Num:50,This is task num:50(29204)
Num:51,This is task num:51(4776)
Num:52,This is task num:52(16868)
Num:53,This is task num:53(21224)
Num:54,This is task num:54(14256)
AddTask, m_cur_thread_count:12 m_max_thread_count:24 m_idle_thread_count:12
Num:55,This is task num:55(28608)
Num:56,This is task num:56(18564)
Num:57,This is task num:57(28596)
Num:58,This is task num:58(8816)
Num:59,This is task num:59(4948)
Thread:Thread_loop_func in, thread NO.13 ID:4872
Num:60,This is task num:60(3604)
Num:61,This is task num:61(23096)
Num:62,This is task num:62(29204)
Num:63,This is task num:63(4776)
Num:64,This is task num:64(16868)
Num:65,This is task num:65(14256)
Num:66,This is task num:66(21224)
AddTask, m_cur_thread_count:13 m_max_thread_count:24 m_idle_thread_count:13
Num:67,This is task num:67(28608)
Num:68,This is task num:68(18564)
Num:69,This is task num:69(28596)
Num:70,This is task num:70(8816)
Num:71,This is task num:71(4948)
Num:72,This is task num:72(4872)
Thread:Thread_loop_func in, thread NO.14 ID:15820
Num:73,This is task num:73(3604)
Num:74,This is task num:74(23096)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:75,This is task num:75(29204)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:76,This is task num:76(4776)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:77,This is task num:77(16868)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:78,This is task num:78(14256)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:79,This is task num:79(21224)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:80,This is task num:80(28608)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:81,This is task num:81(18564)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:82,This is task num:82(28596)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:83,This is task num:83(8816)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:84,This is task num:84(4948)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:85,This is task num:85(4872)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:86,This is task num:86(15820)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:87,This is task num:87(3604)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:88,This is task num:88(23096)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:89,This is task num:89(29204)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:90,This is task num:90(4776)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:91,This is task num:91(16868)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:92,This is task num:92(14256)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:93,This is task num:93(21224)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:94,This is task num:94(28608)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:95,This is task num:95(18564)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:96,This is task num:96(28596)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:97,This is task num:97(8816)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:98,This is task num:98(4948)
Thread:Thread_Check_func notify, m_idle_thread_count;14
Num:99,This is task num:99(4872)
Thread:Thread_loop_func out, thread;4948
Thread:Thread_loop_func out, thread;8816
Thread:Thread_loop_func out, thread;28596
Thread:Thread_loop_func out, thread;18564
Thread:Thread_loop_func out, thread;28608
Thread:Thread_loop_func out, thread;21224
Thread:Thread_loop_func out, thread;14256
Thread:Thread_loop_func out, thread;4776
Thread:Thread_loop_func out, thread;16868
Thread:Thread_loop_func out, thread;23096
Thread:Thread_loop_func out, thread;29204
Thread:Thread_loop_func out, thread;15820
Thread:Thread_loop_func out, thread;3604
Thread:Thread_loop_func out, thread;4872
Threadpool all task finished and threads quit.
Thread:Thread_Check_func out, thread;25800

D:\SourceCode\ThreadPool-simple\ThreadPool-simple\x64\Debug\ThreadPool-simple.exe (进程 22104)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

因为有空闲线程的控制,所以实际上就开了15个线程,没有达到最大线程(这里没有输出,根据内核数12计算的最大线程是25个)。
而且过了初期创建线程的时间开销以后,其实一直都是1个线程来处理的。可以看到
Num:99,This is task num:99(4872)这样的输出中,括号中的线程ID是变化的,而且每次监视线程通知时的log,m_idle_thread_count空闲线程数量稳定在14(从0开始)。

线程池的优化

自动减少线程

上面的测试中,可以看到,由于我添加的任务太简单,开了15个线程,实际后期只需要
1个线程就可以了。
那有可能,有的线程会一直空闲。
一次可以引入一个keepalive超时机制。
当线程空闲时开始计时,超时后,进入线程销毁步骤。

线程池的饱和对策

即任务队列满了,线程池也大都了最大线程数量,此时应该如何应对继续过来的任务。
此时可能需要做拒绝对策。

但是我的项目,预期的任务数量有限,不需要考虑满了的问题,所以没有拒绝对策。

有一个线程异常了怎么办

在线程中可能发生异常的地方,用try-catch机制捕捉异常。

无锁队列有没有必要使用

一般的项目,使用互斥量 + 条件变量就可以了,没必要使用无锁队列。
如果项目的吞吐量很高,可以考虑无锁队列。

由于STL的容器是不保证线程安全的,所以比如在使用std::queue时,要达到去除最前面元素的目的往往需要2步:

task = queue.front();
queue.pop();

如果第一个线程刚刚取得front(),还没来得及pop();第二个线程就进来了,也取得了front();此时再切换回第一个线程,进行pop()。
这时候第二个线程就取得了不应该取到的元素,这只是代码层面的,汇编层面可能不止两句代码。
所以我们可以为quque加一个mutex,当有一个线程访问时,mutex lock(),当mutex unlock()的时候,别的线程才可以访问queue。

但是由于每个线程想要访问队列,都需要加锁解锁,是对效率有影响的。就有了对比加锁队列的无锁队列。

无锁队列的原理:
C++无锁编程——无锁队列(lock-free queue)

调度策略

以上的代码中,我用一个std::queue<task>实现了task队列。
这个策略属于FIFO。

也可以设置优先级。
用std::priority_queue,其中添加pair对象,pair中包含优先级信息和task。例如:

struct TaskPriorityCmp
  {
    bool operator()(const ThreadPool::TaskPair p1, const ThreadPool::TaskPair p2)
    {
        return p1.first > p2.first; //first的小值优先
    }
  };
...
  typedef std::vector<std::thread*> Threads;
  typedef std::priority_queue<TaskPair, std::vector<TaskPair>, TaskPriorityCmp> Tasks;
  来源:[基于C++11实现线程池的工作原理](基于C++11实现线程池的工作原理)

线程任务执行状态的跟踪

以上代码中,只是把任务缓存在队列中,线程来去任务做。做没做成功,并不知道。
可以在线程池中设计一个跟踪任务执行状况的部分,这样也好和主线程通信。

以上代码中,使用了std::bind来把类成员函数作为线程函数执行。
bind时,把类的this指针作为参数,绑定进去了。
所以在线程函数中,还是可以访问线程池的类成员的。
这就为线程池跟踪task执行状态提供了可能。

m_thread_list.emplace_back(std::bind( &ThreadPoolMgr::Thread_loop_func, this));

比如,在线程池中,弄一个完成队列,把执行完的task和完成状态放进去,
主线程可以获取队列中的元素,这样主线程就既可以添加任务,又可以获取任务完成结果了。

线程池的性能测试

目前没想好怎么写…后面再补。

其他

多线程锁的类型

互斥锁、条件变量、自旋锁、读写锁、递归锁。

线程安全和STL容器

STL容器是不保证线程安全的,这也是上面代码为任务队列std::queue加了锁的原因。

使用多线程的时候应该注意什么

  1. 避免死锁
  2. 不要在外部强制结束线程,这样可能导致内存泄漏等资源管理问题,或者程序崩溃。应该在线程内设定,一定的条件,让线程释放资源后自己结束。

如何优雅的退出线程

确保任务执行结束、回收资源、返回执行结果

std::bind

代码中使用std::bind(),把类成员函数,传递给线程构造函数了。
如果不使用std::bind(),编译会报错。

bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。

C++11中的std::bind 简单易懂

join()的位置

比如这个工程的代码,多线程是放在Vector中的,要清空Vector前,需要join线程。

	Join(); //如果不join就析构线程对象,会崩溃。
	if(!m_thread_list.empty())
		m_thread_list.clear();

std::automic

以上代码中,有2处int变量,定义成了atomic。

	std::atomic<int> m_idle_thread_count;
	std::atomic<int> m_cur_thread_count;

因为他们是线程创建,或者空闲和忙碌切换时,需要频繁访问的。
单纯int变量,可能在线程竞争时导致问题。
用原子变量就可以避免这个问题,又避免了使用mutex的麻烦。

参考文章

基于C++11实现线程池的工作原理

为什么要使用多线程?创建多少个线程合适?什么是线程池?

基于C++11的线程池(threadpool),简洁且可以带任意多的参数

轻松掌握C++线程池:从底层原理到高级应用

C++无锁编程——无锁队列(lock-free queue)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值