多线程受限可以利用多核,其次可以在一定程度上避免阻塞产生,当然 cpp中的async 对io类的阻塞可能开销更小一点。
kiraYuukiAsuna的个人空间, 本文大部分内容来源是这位 up 主, 讲的我觉得挺好的.
std::thread
这里要说的不多, 主要就是 std::thread 这个类, 初始化的话需要传入函数名和参数列表, 当然还有其他构造函数, 可以看 cppreference
针对这个类有两个重要的概念, 假如说当前有一个线程叫 thread1
join: 或者叫 wait 更合适一点, 如果thread1.join()被调用, 就会阻塞当前的线程, 直到thread1执行完毕, 子线程还没做完事, 主线程结束了不是很尴尬嘛.detach: 将当前的线程和thread1完全割裂
一个线程在现代的机器上对应一个 cpu 核心, 当我关掉一个线程, 对应核心的利用率就会飞速下降.

如果线程函数有引用类型的参数, 需要用 std::ref 包装一下, 详情可以看: c++ - Passing object by reference to std::thread in C++11 - Stack Overflow 如果多个线程访问公共资源, 记得加锁
性能优化 condition_variable
之前我们看到了, 一个线程跑满的情况下对 cpu 的占用率是十分恐怖的, 一直处于自旋转状态的 mutex 会造成 cpu 资源的极大浪费, 所以为了缓解这种情况, 有以下两种措施可供选择:
- sleep 一段时间, 但问题是这段时间该有多长呢? 所以这种方式并不好
condition_variable与锁(如mutex)配合使用,其核心在于利用了操作系统的内核级等待/唤醒机制。当条件不满足时,等待的线程会被置于休眠(或阻塞)状态并释放锁,完全不占用 CPU 时间。直到其他线程满足了条件并通过notify发出通知,操作系统才会将其唤醒。这种机制从根本上避免了因循环检查(忙等待)导致的 CPU 资源浪费。
#include <iostream>
#include <condition_variable>
#include <mutex>
#include <deque>
#include <thread>
#include <random>
using namespace std::literals::chrono_literals;
std::condition_variable cv;
std::mutex m;
int i = 0;
[[noreturn]] void producer(std::deque<int>& v)
{
while (true)
{
// std::this_thread::sleep_for(1s);
std::unique_lock<std::mutex> lock(m);
v.push_back(i++);
cv.notify_one();//唤醒第一个进入等待状态的线程
}
}
[[noreturn]] void consumer(std::deque<int>& v, std::string cid)
{
while (true)
{
// 条件变量都是与锁配合使用的
std::unique_lock<std::mutex> lock(m);
while (v.empty()) //注意这里一定要是while,不能是if(会产生虚假唤醒),wait_for自带while
{
cv.wait(lock);
}
const int k = v.front();
v.pop_front();
std::cout << cid << " get num: " << k << std::endl;
}
};
int main(int argc, char* argv[])
{
std::deque<int> que;
std::thread p(producer, std::ref(que));
std::thread c1(consumer, std::ref(que), "c1");
std::thread c2(consumer, std::ref(que), "c2");
p.join();
c1.join();
c2.join();
}上面提到的虚假唤醒会使得程序 crash, 因为另一个线程已经提前取走了被唤醒线程本来应该拿到的资源, 具体报错如下:

cpp20 标准中还有 semaphore 这种东西, 有兴趣可以了解一下
线程同步 promise&future
上面我们为了等待子线程处理完所有数据得到正确的 interface 数组的过程中,写了很多臃肿的代码,事实上通过 promise 和 future 这两个模板类,就可以方便的异步获取变量了。
void DoSomething(const int idx, std::vector<int>& interface, std::promise<void>& in)
{
for (int i = 0; i < idx; i++)
{
std::this_thread::sleep_for(200ms);
interface.push_back(i);
}
in.set_value();
}
int main()
{
std::promise<void> pro;
auto f = pro.get_future();
std::vector<int> interface;
std::thread td(DoSomething, 10, std::ref(interface), std::ref(pro));
f.wait();
for (const int value : interface) std::cout << value << " ";
std::cout << std::endl;
td.join();
return 0;
}packaged_task
这个模板类可以帮助我们将函数于返回值在多线程之间实现交互,譬如:
double compute(double num)
{
return std::sin(num);
}
int main()
{
std::packaged_task<double(double)> task(compute);
std::thread t([&] { task(3.1415926); });
// 没错,就是与future联系在了一起
std::cout << task.get_future().get() << std::endl;
t.join();
}死锁问题
可以用 RAII 的方式来解决, 利用 std::lock_guard 可以针对某种情况下忘记 unlock 而产生的死锁.
而由加锁顺序产生的死锁可以用 std::lock 统一加锁来解决.
也可以将资源改成 atomic 类型来避免使用锁.
事实上死锁问题很多时候是由于协同开发产生的,你不能掌控其他人写的代码。