线程池初步了解

提示:记录初步对线程池的学习了解


前言

做mapview的时候涉及到线程池的相关知识,今天学习一下,并做记录。

version1.0  2023年6月14日14:35:22


一、C++线程thread

 参考:https://www.cnblogs.com/kiwiblog/p/14187775.html

一、创建线程

下面代码创建一个子线程,即thread_task,同时还有一个main主线程,目前主要针对这两个线程进行分析

// ConsoleApplication3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <thread>
#include <string>
using namespace std;

//子线程任务函数
void thread_task()
{
	//cout << "hello thread" << endl;

	for (int i = 0; i < 10; i++)
	{
		cout << "print thread: " << i << endl;
	}
}

int main()
{
    std::cout << "Hello World!\n";

	thread t(thread_task);        //创建子线程
	
	//cout << "thread end" << endl;
	for (int i = 0; i > -10; i--)
	{
		cout << "print main before thread: " << i << endl;
	}

	t.join();		//join 这之前的主线程和子线程并发进行
	for (int i = 0; i > -10; i--)
	{
		cout << "print main: " << i << endl;
	}
	
	return 0;
}


//打印记录如下
//Hello World!
//print thread : print main before thread : 0
//print main before thread : -1
//print main before thread : -2
//print main before thread : -3
//print main before thread : -4
//print main before thread : -5
//0
//print thread : 1
//print thread : 2
//print thread : 3
//print thread : 4
//print thread : 5
//print thread : 6
//print thread : 7
//print thread : 8
//print main before thread : -6
//print thread : 9
//print main before thread : -7
//print main before thread : -8
//print main before thread : -9
//print main : 0
//print main : -1
//print main : -2
//print main : -3
//print main : -4
//print main : -5
//print main : -6
//print main : -7
//print main : -8
//print main : -9

//对打印记录做简单解释,每次的打印情况是不一样的,根据操作系统的调度情况。
//这个代码主要是用于检验.join的功能。如代码所示,join前的线程是并行的,join之后的线程是串行的。

一开始觉得线程池很复杂,需要移植大量代码,实际上看起来只需要一个#include <thread>就行了,还挺方便的。

这个代码也可以查看,真不错啊!

下面就是涉及到信号量的分析。为什么需要信号量呢?可能是因为前面打印结果中,主线程和子线程无序打印吧。

二、创建信号量

#include <iostream>
#include <thread>
#include <atomic>
#include <time.h>
#include <mutex>
using namespace std;

#define MAX 100000
#define THREAD_COUNT 20

//原子操作
//atomic_int total(0);

int total = 0;
mutex mt;

void thread_task1()
{
	for (int i = 0; i < 10; i++)
	{
		mt.lock();
		cout << "print task1: " << i << endl;
		mt.unlock();
	}
}

void thread_task2()
{
	for (int i = 0; i < 10; i++)
	{
		mt.lock();
		cout << "print task2: " << i << endl;
		mt.unlock();
	}
}

int main()
{
	clock_t start = clock();
	thread t[THREAD_COUNT];

	thread t1(thread_task1);
	thread t2(thread_task2);
	t1.join();
	t2.join();

	clock_t finish = clock();
	// 输出结果
	cout << "result:" << total << endl;
	cout << "duration:" << finish - start << "ms" << endl;

	return 0;
}

//打印
//print task1 : 0
//print task1 : 1
//print task1 : 2
//print task1 : 3
//print task1 : 4
//print task1 : 5
//print task1 : 6
//print task1 : 7
//print task1 : 8
//print task1 : 9
//print task2 : 0
//print task2 : 1
//print task2 : 2
//print task2 : 3
//print task2 : 4
//print task2 : 5
//print task2 : 6
//print task2 : 7
//print task2 : 8
//print task2 : 9
//result : 0
//duration : 7ms

//突然发现自己的代码没有实现线程切换,好丧啊

 找到一个可以交替执行的代码,是使用信号量控制的

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

mutex mtx;
condition_variable cv;
string flag("A");

void PrintA()
{
	while (true) {
		std::unique_lock<std::mutex> lck(mtx);
		while (flag == "B") {
			cv.wait(lck);
		}
		//std::this_thread::sleep_for(std::chrono::seconds(1));
		std::cout << "A" << std::endl;
		flag = "B";
		cv.notify_all();
	}
}

void PrintB()
{
	while (true) {
		std::unique_lock<std::mutex> lck(mtx);
		while (flag == "A") {
			cv.wait(lck); // cv.wait(lck, lambda)
		}
		//std::this_thread::sleep_for(std::chrono::seconds(1));
		std::cout << "B" << std::endl;
		flag = "A";
		cv.notify_all();
	}
}

int main()
{
	std::thread t1(PrintA);
	std::thread t2(PrintB);
	t1.join();
	t2.join();
	std::cin.get();
}

还有另外一个

#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>

std::mutex data_mutex;
std::condition_variable data_var;
bool label = false;

void printodd()
{
	std::unique_lock<std::mutex> ulock(data_mutex);
	for (int odd = 1; odd <= 100; odd += 2)
	{
		data_var.wait(ulock, [] {return label; });
		std::cout << std::this_thread::get_id() << ": " << odd << " labelA= " << label << std::endl;
		label = false;
		data_var.notify_one();
	}
}

void printeven()
{
	std::unique_lock<std::mutex> ulock(data_mutex);
	for (int even = 0; even < 100; even += 2)
	{
		data_var.wait(ulock, [] {return !label; });
		std::cout << std::this_thread::get_id() << ": " << even << " labelB= " << label << std::endl;
		label = true;
		data_var.notify_one();
	}
}

int main()
{
	std::thread t1(printeven);
	std::thread t2(printodd);
	t1.join();
	t2.join();
	std::cout << "end!" << std::endl;
	getchar();
	return 0;
}

所以是之前那个文档干扰了我?那个文档有问题?

后来搜索线程交替运行,都是用的这种方法。太坑了啊!!!还是找本书看下!

搜了下c++ primary plus那个书上也没有介绍多线程的啊!

找个详细介绍thread的文档看看。

2023年6月15日14:14:15

void thread_task()
{
	int dat = 0;
	for (int i = 0; i < MAX; i++)
	{
		mt.lock();
		dat += 1;
		cout << "thread_id:" << this_thread::get_id() << "result1:" << total << endl;
		mt.unlock();
	}
}

//打印结果
//thread_id:19756result1 : 45978
//thread_id : 19756result1 : 45979
//thread_id : 19756result1 : 45980
//thread_id : 19756result1 : 45981
//thread_id : 19756result1 : 45982
//thread_id : 2056result1 : 45983
//thread_id : 2056result1 : 45984
//thread_id : 2056result1 : 45985
//thread_id : 2056result1 : 45986
//thread_id : 2056result1 : 45987
//thread_id : 2056result1 : 45988

由此可见,实际运行过程中,不是进入task1线程for循环执行完,而是一直进task1线程,直到执行完。所以比较起之前ucos线程,task1没有阻塞,所以一直做?

我刚才百度了为什么要创建线程,回答是提高cpu利用率。

但是如下这个代码,task2竟然都没有执行完。。。真迷啊

#include <iostream>
#include <thread>
#include <atomic>
#include <time.h>
#include <mutex>
using namespace std;

#define MAX 100000
#define THREAD_COUNT 20

int total = 0;
mutex mt;
int counter = 0;

void thread_task1()
{
	while (counter < 10)
	{
		mt.lock();
		counter++;
		cout << "thread_task1:" << counter << endl;
		mt.unlock();
	}
}

void thread_task2()
{
	while (counter < 10)
	{
		mt.lock();
		counter++;
		cout << "thread_task2:" << counter << endl;
		mt.unlock();
	}
}

int main()
{
	clock_t start = clock();

	thread t1(thread_task1);
	thread t2(thread_task2);

	t1.join();
	t2.join();
	
	for (int i = 0; i < 10; i++)
	{
		mt.lock();
		cout << "print main: " << i << endl;
		mt.unlock();
	}
	clock_t finish = clock();
	// 输出结果
	cout << "result:" << total << endl;
	cout << "duration:" << finish - start << "ms" << endl;
	
	return 0;
}

还是没有理清楚,但是生产者消费者模式到时可以理解。然后想到ucos是可以设置时间片的。到了时间片自动切换任务。或者因为等待切换时间片。 

三、条件变量

回到最初参考的那个文章

C++中提供了#include <condition_variable>头文件,里面就包含了条件变量的相关类。其中有两个非常重要的接口,wait()和notify_one(),wait()可以让线程陷入休眠状态,意思就是不干活了,notify_one()就是唤醒真正休眠状态的线程,开始干活了。当然还有notify_all()这个接口,顾名思义,就是通知所有正在等待的线程,起来干活了。

这个也就是我想交替与运行线程的解决办法。

这时候还是回到那个问题,为什么一开始创建的两个线程是一个先执行完了,后面才执行?从查找百度得到的只言片语,可能是一个线程竞争性太强了?

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;

deque<int> q;
mutex mt;
condition_variable cond;

void thread_producer()
{
	int count = 10;
	while (count > 0)
	{
		unique_lock<mutex> unique(mt);
		q.push_front(count);
		unique.unlock();
		cout << "producer a value: " << count << endl;
		cond.notify_one();
		this_thread::sleep_for(chrono::seconds(1));
		count--;
	}
}

void thread_consumer()
{
	int data = 0;
	while (data != 1)
	{
		unique_lock<mutex> unique(mt);
		while (q.empty())
			cond.wait(unique);
		data = q.back();
		q.pop_back();
		cout << "consumer a value: " << data << endl;
		unique.unlock();
	}
}

int main()
{
	thread t1(thread_consumer);
	thread t2(thread_producer);
	t1.join();
	t2.join();
	return 0;
}

四、线程池的实现

#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <condition_variable>
using namespace std;

const int MAX_THREADS = 1000; //最大线程数目

template <typename T>
class threadPool
{
public:
	threadPool(int number = 1);
	~threadPool();
	bool append(T *task);
	//工作线程需要运行的函数,不断的从任务队列中取出并执行
	static void *worker(void *arg);
	void run();

private:
	//工作线程
	vector<thread> workThread;
	//任务队列
	queue<T *> taskQueue;
	mutex mt;
	condition_variable condition;
	bool stop;
};

template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
	if (number <= 0 || number > MAX_THREADS)
		throw exception();
	for (int i = 0; i < number; i++)
	{
		cout << "create thread:" << i << endl;
		workThread.emplace_back(worker, this);
	}
}
template <typename T>
inline threadPool<T>::~threadPool()
{
	{
		unique_lock<mutex> unique(mt);
		stop = true;
	}
	condition.notify_all();
	for (auto &wt : workThread)
		wt.join();
}
template <typename T>
bool threadPool<T>::append(T *task)
{
	//往任务队列添加任务的时候,要加锁,因为这是线程池,肯定有很多线程
	unique_lock<mutex> unique(mt);
	taskQueue.push(task);
	unique.unlock();
	//任务添加完之后,通知阻塞线程过来消费任务,有点像生产消费者模型
	condition.notify_one();
	return true;
}
template <typename T>
void *threadPool<T>::worker(void *arg)
{
	threadPool *pool = (threadPool *)arg;
	pool->run();
	return pool;
}
template <typename T>
void threadPool<T>::run()
{
	while (!stop)
	{
		unique_lock<mutex> unique(this->mt);
		//如果任务队列为空,就停下来等待唤醒,等待另一个线程发来的唤醒请求
		while (this->taskQueue.empty())
			this->condition.wait(unique);
		T *task = this->taskQueue.front();
		this->taskQueue.pop();
		if (task)
			task->process();
	}
}
#endif



//#include "threadPool.h"
#include <string>
using namespace std;
class Task
{
private:
	int total = 0;

public:
	void process();
};

//任务具体实现什么功能,由这个函数实现
void Task::process()
{
	//这里就输出一个字符串
	cout << "task successful!" << endl;
	this_thread::sleep_for(chrono::seconds(1));
}

template class std::queue<Task>;
int main(void)
{
	threadPool<Task> pool(1);
	std::string str;
	while (1)
	{
		Task *task = new Task();
		pool.append(task);
		delete task;
	}
}

2023年12月28日16:26:01

今天重新看mapview代码,然后又看了一遍线程池。把上面的代码在vs中跑了一次,可以运行。现在再回顾一遍线程池的代码。

产生了几个疑问:

1、main函数中threadPool创建的数量是1,如果创建3有什么不同?

2、threadPool析构函数中的wt.join()什么意思,join不是等待线程结束吗?

同时也有了一些新的理解。

1、比如run方法中this->condition.wait(unique);是等待唤醒,调用的是threadPool中的成员condition_variable condition;中的wait方法。

2、threadPool中的number指的是创建几个workThread,workThread是thread型的vector集合。

那么workThread中push进的是什么呢?是线程函数worker。即workThread.emplace_back(worker, this);

那worker中调用的有时啥呢,是函数run()。run函数判断taskQueue中是否有任务了,有的话就执行,没有的话就等待。看了下run函数是while函数。所以是创建了3个while循环吗?

那么问题来了,3个while是异步的还是同步的?

目前的理解可能是因为worker转成了thread,所以变成了线程,所以每个worker中的while都是一个线程,是异步执行的。

2024年6月4日10:21:26 更新   回答上述疑问

1、如果创建3有什么不同?答:如果创建三个就是创建3个线程,执行task队列会更快。

2、threadPool析构结束线程需要等所有线程都执行完。

二、个人理解

1、变化代码

#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <condition_variable>
using namespace std;
 
const int MAX_THREADS = 1000; //最大线程数目
 
template <typename T>
class threadPool
{
public:
	threadPool(int number = 1);
	~threadPool();
	bool append(T *task);
	//工作线程需要运行的函数,不断的从任务队列中取出并执行
	static void *worker(void *arg);
	static void *worker1(void *arg);
	static void *worker2(void *arg);
	static void *worker3(void *arg);
	void run();
	void run1();
	void run2();
	void run3();
 
private:
	//工作线程
	vector<thread> workThread;
	//任务队列
	queue<T *> taskQueue;
	mutex mt;
	condition_variable condition;
	bool stop;
};
 
template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
	if (number <= 0 || number > MAX_THREADS)
		throw exception();
	/*for (int i = 0; i < number; i++)
	{
		cout << "create thread:" << i << endl;
		workThread.emplace_back(worker, this);
	}*/
	workThread.emplace_back(worker, this);
	workThread.emplace_back(worker1, this);
	workThread.emplace_back(worker2, this);
	workThread.emplace_back(worker3, this);
}
template <typename T>
inline threadPool<T>::~threadPool()
{
	cout << "threadPool destruct" << endl;
	{
		unique_lock<mutex> unique(mt);
		stop = true;
	}
	condition.notify_all();
	for (auto &wt : workThread)
		wt.join();
}
template <typename T>
bool threadPool<T>::append(T *task)
{
	//往任务队列添加任务的时候,要加锁,因为这是线程池,肯定有很多线程
	unique_lock<mutex> unique(mt);
	taskQueue.push(task);
	unique.unlock();
	//任务添加完之后,通知阻塞线程过来消费任务,有点像生产消费者模型
	condition.notify_one();
	return true;
}
template <typename T>
void *threadPool<T>::worker(void *arg)
{
	threadPool *pool = (threadPool *)arg;
	pool->run();
	return pool;
}
template <typename T>
void *threadPool<T>::worker1(void *arg)
{
	threadPool *pool = (threadPool *)arg;
	pool->run1();
	return pool;
}
template <typename T>
void *threadPool<T>::worker2(void *arg)
{
	threadPool *pool = (threadPool *)arg;
	pool->run2();
	return pool;
}
template <typename T>
void *threadPool<T>::worker3(void *arg)
{
	threadPool *pool = (threadPool *)arg;
	pool->run3();
	return pool;
}
template <typename T>
void threadPool<T>::run()
{
	while (!stop)
	{
		unique_lock<mutex> unique(this->mt);
		//如果任务队列为空,就停下来等待唤醒,等待另一个线程发来的唤醒请求
		while (this->taskQueue.empty())
			this->condition.wait(unique);
		T *task = this->taskQueue.front();
		this->taskQueue.pop();
		if (task)
			task->process();
	}
}
template <typename T>
void threadPool<T>::run1()
{
	while (!stop)
	{
		unique_lock<mutex> unique(this->mt);
		//如果任务队列为空,就停下来等待唤醒,等待另一个线程发来的唤醒请求
		while (this->taskQueue.empty())
			this->condition.wait(unique);
		T *task = this->taskQueue.front();
		this->taskQueue.pop();
		if (task)
			task->process1();
	}
}
template <typename T>
void threadPool<T>::run2()
{
	while (!stop)
	{
		unique_lock<mutex> unique(this->mt);
		//如果任务队列为空,就停下来等待唤醒,等待另一个线程发来的唤醒请求
		while (this->taskQueue.empty())
			this->condition.wait(unique);
		T *task = this->taskQueue.front();
		this->taskQueue.pop();
		if (task)
			task->process2();
	}
}
template <typename T>
void threadPool<T>::run3()
{
	while (!stop)
	{
		unique_lock<mutex> unique(this->mt);
		//如果任务队列为空,就停下来等待唤醒,等待另一个线程发来的唤醒请求
		while (this->taskQueue.empty())
			this->condition.wait(unique);
		T *task = this->taskQueue.front();
		this->taskQueue.pop();
		if (task)
			task->process3();
	}
}
#endif
 
 
 
//#include "threadPool.h"
#include <string>
using namespace std;
class Task
{
private:
	int total = 0;
 
public:
	void process();
	void process1();
	void process2();
	void process3();
};
 
//任务具体实现什么功能,由这个函数实现
void Task::process()
{
	//这里就输出一个字符串
	cout << "task successful!" << endl;
	this_thread::sleep_for(chrono::seconds(1));
}
void Task::process1()
{
	//这里就输出一个字符串
	cout << "task hello!" << endl;
	this_thread::sleep_for(chrono::seconds(1));
}
void Task::process2()
{
	//这里就输出一个字符串
	cout << "task world!" << endl;
	this_thread::sleep_for(chrono::seconds(1));
}
void Task::process3()
{
	//这里就输出一个字符串
	cout << "task every one!" << endl;
	this_thread::sleep_for(chrono::seconds(1));
}
 
template class std::queue<Task>;
int main(void)
{
	threadPool<Task> pool(4);
	std::string str;
	int count = 10;
	while (count--)
	{
		Task *task = new Task();
		pool.append(task);
		delete task;
	}
 
	return 0;
}

这段代码我是让4个线程都执行不一样的动作。目前来看四个线程都执行了。但是线程执行时无序的,虽然10个任务都执行了。所以到底啥是线程啊?我之前GD看到的时间片顺序执行其实不是线程真实的样子?就是乱序执行的吗?

打印信息:

//task successful!
//task successful!
//task hello!
//task world!
//task successful!
//task hello!
//threadPool destructtask successful!
//
//task successful!
//task successful!
//task successful!

2、线程同步

一文搞定c++多线程同步机制_c++多线程同步等待-CSDN博客

这个文章提到了线程同步,但是是利用两个线程互斥同步的。那么多个线程怎么线程同步呢?

使用lock_guard<mutex> l(mtx);也不能实现多线程同步

c++ thread创建与多线程同步详解_std::thread同步-CSDN博客

总结:

这边写总结

代码如下(示例):

参考 

参考自C++11多线程编程(六)——线程池的实现

thread多线程整理文档C++ thread用法总结(整理)_顺其自然~的博客-CSDN博客


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值