线程的一些基本概念
- 线程阻塞
线程阻塞是一种状态,可以通过多种方式实现阻塞(sleep_for(), condition_variable)。线程阻塞并不是单纯的指线程程序停在某一位置不向下执行,而是一种操作系统支持的高效线程调度机制,线程阻塞状态发生后不再参与竞争cpu时间片,直到某种事件发生结束阻塞状态。所以阻塞状态的线程不会消耗系统资源,为了区分这里列出两种能使线程暂停但是并没有阻塞线程的例子:
上面的例子while()循环里是一个空循环,导致了cpu的不合理消耗。可能有人会想到在while()中使用// 此段代码演示子线程里while(condition)实现的线程暂停,这种方法会耗尽cpu的一个硬件核心。 // 属于非常不合理的编程方式,应该禁止使用 #include <iostream> #include <thread> using namespace std; bool pause_flg = true; // 全局状态标志 [[noreturn]] void do_work() // [[noreturn]]是状态变量,告诉编译器此函数永远不会返回 { while (pause_flg) { // this_thread::yield(); ; } cout << "Subthread is running!" << endl; } int main() { thread trd_1(do_work); cout << "Mian thread is running!" << endl; this_thread::sleep_for(chrono::seconds(20)); // 等待20s,此刻cpu的一个核心将满载 pause_flg = false; trd_1.join(); return 0; }
this_thread::yield();
交出cpu的使用权,以为这样可以避免cpu消耗。其实是错误的,把上面的代码修改一下:
这样发现cpu还是满载一个核心,这是因为while (pause_flg) { // 反注释这一行 this_thread::yield(); // 放弃cpu的此次执行权 // ; }
this_thread::yield();
结束后我们的线程还是保持资源竞争权力的,系统就会立刻重新给我们的线程分配资源,然后我们的线程又放弃执行权,如此反复,cpu也就在频繁的线程调度中消耗资源。
【正确的线程阻塞方式】
应该用condition_variable
条件变量,参考:C++11 并发指南五(std::condition_variable 详解)#include <iostream> #include <thread> #include <mutex> #include <condition_variable> using namespace std; std::mutex mtx; // 全局互斥锁. std::condition_variable cv; // 全局条件变量. void do_work() { unique_lock<mutex> lck(mtx); cv.wait(lck); cout << "Subthread is running!" << endl; } int main() { thread trd_1(do_work); cout << "Mian thread is running!" << endl; this_thread::sleep_for(chrono::seconds(20)); // trd_1线程通过cv.wait(lck)挂起,所以不会占用cpu资源 cv.notify_all(); trd_1.join(); return 0; }
std::thread类
std::thread类是c++11引入标准库的。使得C++编写跨平台多线程程序更加方便。下面介绍一下基本用法。
构造函数
待写。。。
常用功能
线程池
一个简易的线程池实现:
// file name: ThreadPool.hpp
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <utility>
#include <vector>
class ThreadPool {
private:
// 线程池最大线程数量
const uint32_t MAX_THREAD_NUM = 256;
void looprun()
{
std::function<void()> func; //定义基础函数类func
bool if_success {false}; // 是否成功取出任务
//判断线程池是否关闭,没有关闭,循环提取
while (!this->m_shutdown)
{
{
std::unique_lock<std::mutex> lock(this->m_conditional_mutex); // 加锁
//如果任务队列为空,阻塞当前线程
if (this->tasks.empty()) {
this->m_conditional_lock.wait(lock); //等待条件变量通知,开启线程
}
if (this->m_shutdown)
return;
//取出任务队列中的元素
if (!tasks.empty()) {
func = std::move(this->tasks.front()); // 取一个 task
this->tasks.pop();
if_success = true;
} else{
if_success = false;
}
}
//如果成功取出,执行工作函数
if (if_success) {
func();
}
}
}
std::atomic<bool> m_shutdown; // 线程池是否关闭
std::queue<std::function<void()>> tasks; // 任务队列, std::queue线程不安全,操作需要加锁
std::vector<std::thread> threads; // 工作线程队列
std::mutex m_conditional_mutex; // 线程休眠锁互斥变量
std::condition_variable m_conditional_lock; // 线程环境锁,让线程可以处于休眠或者唤醒状态
public:
//线程池构造函数
ThreadPool(const int n_threads) : threads(std::vector<std::thread>(n_threads)), m_shutdown(false)
{
if (n_threads > MAX_THREAD_NUM) { std::cout << "警告: 线程池过大,建议重新构造线程池!" << std::endl; }
for (int i = 0; i < threads.size(); ++i) {
threads[i] = std::thread([this](){ this->looprun(); }); //分配工作线程
}
}
ThreadPool(const ThreadPool &) = delete; //拷贝构造函数,并且取消默认父类构造函数
ThreadPool(ThreadPool &&) = default; // 拷贝构造函数,允许右值引用
ThreadPool & operator=(const ThreadPool &) = delete; // 赋值操作
ThreadPool & operator=(ThreadPool &&) = delete; //赋值操作
~ThreadPool()
{
m_shutdown = true;
m_conditional_lock.notify_all(); //通知 唤醒所有工作线程
// Waits until threads finish their current task and shutdowns the pool
for (int i = 0; i < threads.size(); ++i) {
if(threads[i].joinable()) { //判断线程是否正在等待
threads[i].join(); //将线程加入等待队列
}
}
}
/*! Submit a function to be executed asynchronously by the pool
* 不支持直接接受类非静态成员函数。有三种方法可以实现接受类非静态成员函数:
* 1. submit([&](){ dog.sayHello(); });
* 2. submit(std::bind(&Dog::sayHello, &dog));
* 3. submit(std::mem_fn(&Dog::sayHello), &dog);
*
* @tparam F
* @tparam Args
* @param f
* @param args
* @return
*/
template<typename F, typename...Args>
auto submit(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
// Create a function with bounded parameters ready to execute
// 绑定函数和实参
std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
// Encapsulate it into a shared ptr in order to be able to copy construct / assign
auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);
// Wrap packaged task into void function
std::function<void()> wrapper_func = [task_ptr]() {
(*task_ptr)();
};
{
std::unique_lock<std::mutex> lock(m_conditional_mutex); // 加锁
tasks.emplace(wrapper_func); // 压入队列
}
// 唤醒一个等待中的线程
m_conditional_lock.notify_one();
// 返回先前注册的任务指针
return task_ptr->get_future();
}
};
如何使用:
#include <iostream>
#include "string"
#include <random>
#include "ThreadPool.hpp"
using namespace std;
// 全局锁
std::mutex mtx;
// 不返回结果
void print_int(int a)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
unique_lock<mutex> lock(mtx); // 加锁防止打印错乱
cout << "a: " << a << endl;
}
// 直接返回结果
int test_return(int a, int b)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
return a + b;
}
// 通过引用和return返回结果
int test_ref(int a, int& ref_1, int& ref_2)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ref_1 = a + 100;
ref_2 = a + 1000;
return a + 10000;
}
int main()
{
// 创建3个线程的线程池
ThreadPool pool(3);
// 提交10个不返回结果的任务
for (int i = 0; i < 10; i++) {
pool.submit(print_int, i);
}
this_thread::sleep_for(chrono::seconds(5)); // 等待所有任务完成
// 提交带return的函数
auto future1 = pool.submit(test_return, 10, 20);
// 等待任务完成,并取出结果
int res = future1.get();
std::cout << "Last operation result is equals to: " << res << std::endl;
// 通过引用和return返回结果
int output_ref_1, output_ref_2;
auto future2 = pool.submit(test_ref, 8, std::ref(output_ref_1), std::ref(output_ref_2));
// 等待任务完成
int out = future2.get();
std::cout << "output_ref_1: " << output_ref_1 << " output_ref_2: " << output_ref_2 << " output_return: " << out << std::endl;
return 0;
}