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

文章目录

  • 为什么用多线程
  • 为什么使用线程池
  • 设计一个简单的线程池
    • 线程池的结构
    • 线程的数量
    • 线程池的基本逻辑
    • 如何实现线程的复用
    • 如何启动线程和阻塞线程
    • 如何结束线程池,释放线程
    • 线程池代码
  • 线程池的优化
    • 自动减少线程
    • 线程池的饱和对策
    • 有一个线程异常了怎么办
    • 无锁队列有没有必要使用
    • 调度策略
    • 线程任务执行状态的跟踪
  • 线程池的性能测试
  • 其他
    • 多线程锁的类型
    • 线程安全和STL容器
    • 使用多线程的时候应该注意什么
    • 如何优雅的退出线程
    • std::bind
    • join()的位置
    • std::automic
  • 参考文章

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

为什么用多线程

  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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是Vue.js的响应式系统?如何实现数据的双向绑定?

目录 一、Vue.js介绍 二、响应式系统 三、数据的双向绑定 四、如何实现数据的双向绑定 一、Vue.js介绍 Vue.js 是一种用于构建用户界面的渐进式 JavaScript 框架。它由尤雨溪开发并于2014年首次发布。Vue.js 的核心库只关注视图层&#xff0c;其设计灵感来自于 Angular 和…

如何使用Docker安装Spug并实现远程访问本地运维管理界面

文章目录 前言1. Docker安装Spug2 . 本地访问测试3. Linux 安装cpolar4. 配置Spug公网访问地址5. 公网远程访问Spug管理界面6. 固定Spug公网地址 前言 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、文件…

PaddleOCR将自己训练的模型转换为openvino格式模型

1 训练模型 python train_steelseal_det.py2 checkpoints模型转换为inference 模型 加载配置文件ch_PP-OCRv4_det_student_steelseal.yml&#xff0c;从./output/ch_PP-OCRv4/best_model/目录下加载model模型&#xff0c;inference模型保存在./output/ch_PP-OCRv4/best_model…

Pandas ------ 向 Excel 文件中写入含有合并表头的数据

Pandas ------ 向 Excel 文件中写入含有合并表头的数据 推荐阅读引言正文 推荐阅读 Pandas ------ 向 Excel 文件中写入含有 multi-index 和 Multi-column 表头的数据 引言 这里给大家介绍一下如何向 Excel 中写入带有合并表头的数据。 正文 import pandas as pddf1 pd.D…

代码随想录 Leetcode226.翻转二叉树

题目&#xff1a; 代码(首刷看解析 2024年1月25日&#xff09;&#xff1a; class Solution { public:TreeNode* invertTree(TreeNode* root) {if(root nullptr) return root;swap(root->left,root->right);invertTree(root->left);invertTree(root->right);retu…

09.Elasticsearch应用(九)

Elasticsearch应用&#xff08;九&#xff09; 1.搜索结果处理包括什么 排序分页高亮返回指定字段 2.排序 介绍 Elasticsearch支持对搜索结果排序&#xff0c;默认是根据相关度算分来排序 支持排序的字段 keyword数值地理坐标日期类型 排序语法 GET /[索引名称]/_sear…

C# 使用AutoMapper实现类映射

写在前面 AutoMapper是一个用于.NET中简化类之间的映射的扩展库&#xff1b;可以在执行对象映射的过程&#xff0c;省去的繁琐转换代码&#xff0c;实现了对DTO的快速装配&#xff0c;有效的减少了代码量。 通过NuGet安装&#xff0c;AutoMapper&#xff0c; 由于本例用到了D…

Altair SimSolid常见问题解答 衡祖仿真

Q&#xff1a;SimSolid究竟有什么特别之处&#xff1f; A&#xff1a;Altair SimSolid是专为设计工程师开发的结构分析软件且非常有创新性。它消除了传统 FEA 中特别耗时和非常专业的两项庞大任务——几何结构简化和网格划分&#xff0c;是一场仿真变革。简而言之&#xff0c;…

再识C语言 DAY12 【再识函数(上)】

文章目录 前言一、函数是什么&#xff1f;二、自定义函数参数返回值void修饰函数的返回值和参数 函数不能嵌套定义形参和实参的区别return的用法补充if……else if……else……的用法 后面会讲解“函数调用&#xff0c;函数声明以及函数原型&#xff0c;块级变量&#xff0c;归…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-11 HTML5 表单验证

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>HTML5 表单验证</title> </head><body> <form action"#" method"get" novalidate>请输入您的邮箱:<input type&q…

CNN卷积理解

1 卷积的步骤 1 过滤器&#xff08;卷积核&#xff09;&#xff08;Filter或Kernel&#xff09;&#xff1a; 卷积层使用一组可学习的过滤器来扫描输入数据&#xff08;通常是图像&#xff09;。每个过滤器都是一个小的窗口&#xff0c;包含一些权重&#xff0c;这些权重通过训…

DAY11_(简易版)VUEElement综合案例

目录 1 VUE1.1 概述1.1.1 Vue js文件下载 1.2 快速入门1.3 Vue 指令1.3.1 v-bind & v-model 指令1.3.2 v-on 指令1.3.3 条件判断指令1.3.4 v-for 指令 1.4 生命周期1.5 案例1.5.1 需求1.5.2 查询所有功能1.5.3 添加功能 2 Element2.0 element-ui js和css和字体图标下载2.1 …

简析:老隋分享的temu项目怎么做?是蓝海项目吗?

在跨境电商的热潮中&#xff0c;老隋分享的temu项目引起了广泛关注。这个项目究竟有何魅力?它是一个蓝海项目吗?本文将为你揭开temu项目的神秘面纱&#xff0c;并探讨其前景和潜在机会。 首先&#xff0c;我们来了解一下temu项目的基本情况 Temu是拼多多旗下的跨境电商平台&a…

架构篇25:高可用存储架构-双机架构

文章目录 主备复制主从复制双机切换主主复制小结存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。因此,对任何一个高可用存储方案,我们需要从以下几个方面去进行思考…

希尔排序-排序算法

前言 希尔排序固然很好&#xff0c;但是某些情况下&#xff0c;有很多缺点。例如下面这种情况&#xff1a; 9 之前的元素都已经有序&#xff0c;只有元素 1 和 2 的位置不对&#xff0c;使用插入排序几乎要移动整个数组的元素&#xff0c;效率很低。 这时候希尔排序横空出世&…

MySQL和Redis的事务有什么异同?

MySQL和Redis是两种不同类型的数据库管理系统&#xff0c;它们在事务处理方面有一些重要的异同点。 MySQL事务&#xff1a; ACID属性&#xff1a; MySQL是一个关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;支持ACID属性&#xff0c;即原子性&#xff08;Ato…

全球 IPv4 耗尽,下个月开始收费!

哈喽大家好&#xff0c;我是咸鱼 IPv4&#xff08;Internet Protocol version 4&#xff09;是互联网上使用最广泛的网络层协议之一&#xff0c;于1981年在 RFC 791 中发布&#xff0c;它定义了 32 位的IP地址结构和基本的协议操作。 由于 IPv4 使用 32 位的地址&#xff0c;…

深入了解Figure的结构与层次

深入了解Figure的结构与层次 一 Matplotlib中的Figure1.1 Figure的概念和作用:1.2.创建Figure对象:1.3 Figure的属性和方法: 二 子图&#xff08;Axes&#xff09;的角色与创建2.1 子图&#xff08;Axes&#xff09;的概念&#xff1a;2.2 创建子图的方法&#xff1a;2.3 Axes的…

灰度图像的自动阈值分割

第一种&#xff1a;Otsu &#xff08;大津法&#xff09; 一、基于cv2的API调用 1、代码实现 直接给出相关代码&#xff1a; import cv2 import matplotlib.pylab as pltpath r"D:\Desktop\00aa\1.png" img cv2.imread(path, 0)def main2():ret, thresh1 cv2.…

软件产品研发过程 - 三、详细设计

软件产品研发过程 - 三、详细设计 详细设计是在软件开发过程中&#xff0c;基于概要设计&#xff08;将功能按子功能进行拆分&#xff0c;画面跳转关系、UI原型、画面上所有功能点及每个功能点对应的业务流程&#xff09;&#xff0c;以程序开发的角度来设计概要设计中每个功能…
最新文章