提示:记录初步对线程池的学习了解
前言
做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博客
总结:
这边写总结
代码如下(示例):
参考
thread多线程整理文档C++ thread用法总结(整理)_顺其自然~的博客-CSDN博客
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。