前言
测试环境:VS2019 如果做特殊说明,是C++14。
注意:和C#不一样,stl会在构造函数中启动线程,无需手动启动,join是等待函数结束。
cpp
long long g_x = 0;
void fun() {
for (int i = 0; i < 1000 * 1000*1000*2;i++) {
g_x++;
}
}
int main()
{
std::thread th1(fun);
std::thread th2(fun);
th1.join();
th2.join();
}
以上代码执行完毕后,应该是 4 × 10 9 , 实际上是 2.3 × 10 9 4\times 10^9,实际上是2.3 \times 10^9 4×109,实际上是2.3×109。g_x++,可以看成t=g_x,t++,g_x=t。出错的情况,如下:
线程一执行第一行,t=0。
线程二执行第一行,t=0。
线程一二执行t++。
线程一执行g_x=t。g_x等于1。
线程二执行g_x=t。g_x等于1。
Window下,有四种同步异步的方法,临界区(当前进程)、互斥量、事件对象(可发通知消息)、信号量(可以有多个资源)。
同步异步方法
std::mutex
stl的互斥量,在windows下实际是临界区。加锁后,速度变慢,故循环次数变成十分之一。
cpp
#include <thread>
#include <iostream>
#include <mutex>
std::mutex m;
long long g_x = 0;
void fun() {
for (int i = 0; i < 1000 * 1000*100*2;i++) {
m.lock();
g_x++;
m.unlock();
}
}
int main()
{
std::thread th1(fun);
std::thread th2(fun);
th1.join();
th2.join();
}
条件变量
cpp
#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void thread1() {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::lock_guard<std::mutex> lk(mtx);
ready = true;
cv.notify_one();
}
void thread2() {
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, [] { return ready; }); // 阻塞直到 ready == true
std::cout << "worker: got ready\n";
}
int main() {
std::thread t1(thread1), t2(thread2);
t1.join();
t2.join();
}
worker: got ready
改成以下代码
cpp
int main() {
std::thread t1(thread1), t2(thread2) , t3(thread2);
t1.join();
t2.join();
t3.join();
}
输出不变,t3永远不会结束。
cpp
cv.notify_one();
改成
cpp
cv.notify_all();
std::shared_mutex
std::shared_mutex 在 Windows 上的开销比 std::mutex 大。shared_mutex 允许多个读者并行进入临界区,减少了线程的无效阻塞,从而提高了读密集型任务的吞吐量。
测试了若干小程序,实际用时相差不大。
cpp
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include "ConsoleApplication1.h"
volatile long long t = 0;
std::string str;
std::shared_mutex rw_mtx;
void worker1() {
for (int i = 0;i < 100;i++) {
std::unique_lock<std::shared_mutex>(rw_mtx);
str = str + 'a';
}
}
void worker2() {
for (int i = 0;i < 300;i++) {
std::shared_lock<std::shared_mutex>(rw_mtx);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
for (const auto& ch : str)
{
t += ch;
}
}
}
void Test()
{
std::thread th1(worker1);
std::vector<std::thread> ths;
for (int i = 0;i < 500;i++) {
ths.emplace_back(std::thread(worker2));
}
th1.join();
for (auto& th : ths) {
th.join();
}
}
int main() {
auto start = std::chrono::high_resolution_clock::now();
Test();
Test();
Test();
Test();
Test();
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "总耗时: " << elapsed << " ms" << std::endl;
std::cout << "t: " <<t << std::endl;
int tmp;
std::cin >> tmp;
return 0;
}
总耗时: 23855 ms
t: 21825000000
cpp
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include "ConsoleApplication1.h"
volatile long long t = 0;
std::string str;
std::shared_mutex rw_mtx;
void worker1() {
for (int i = 0;i < 100;i++) {
std::unique_lock<std::shared_mutex>(rw_mtx);
str = str + 'a';
}
}
void worker2() {
for (int i = 0;i < 300;i++) {
std::shared_lock<std::shared_mutex>(rw_mtx);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
for (const auto& ch : str)
{
t += ch;
}
}
}
void Test()
{
std::thread th1(worker1);
std::vector<std::thread> ths;
for (int i = 0;i < 500;i++) {
ths.emplace_back(std::thread(worker2));
}
th1.join();
for (auto& th : ths) {
th.join();
}
}
int main() {
auto start = std::chrono::high_resolution_clock::now();
Test();
Test();
Test();
Test();
Test();
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "总耗时: " << elapsed << " ms" << std::endl;
std::cout << "t: " <<t << std::endl;
int tmp;
std::cin >> tmp;
return 0;
}
总耗时: 24311 ms
t: 21825000000
锁
std::lock_guard 和 std::unique_lock 都是 C++ 标准库 提供的RAII 风格互斥锁管理工具,RAII 全称是 Resource Acquisition Is Initialization,通俗地说在构造函数中加锁,析构函数中解锁。
std::lock_guard
只有构造函数和析构,拷贝构造函数和等于被删除。
cpp
#include <thread>
#include <iostream>
#include <mutex>
std::mutex m;
long long g_x = 0;
void fun() {
for (int i = 0; i < 1000 * 1000*100*2;i++) {
std::lock_guard<std::mutex> lock(m);
g_x++;
}
}
int main()
{
std::thread th1(fun);
std::thread th2(fun);
th1.join();
th2.join();
}
加锁后,结果正确。
try_lock的应用场景,有些次要任务可以忽略。比如:某些设备需要换料,故每60分钟一定有30秒空闲,利用空闲可以做一下维护工作。比如:某些次要日志。
std::unique_lock
换成唯一锁也正确。
cpp
std::unique_lock<std::mutex> lock(m);
cpp
_NODISCARD_CTOR unique_lock(_Mutex& _Mtx, adopt_lock_t)
unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
_NODISCARD_CTOR unique_lock(_Mutex& _Mtx, try_to_lock_t)
以上三个版本的构造函数,分别是:加锁,不加锁,尝试加锁。
接下来三个版本,是时间有关。
移动构造函数和移动赋值,容许转移控制权。
赋值构造函数和赋值函数,擅长。
手动加锁、解锁。
交换函数。
信号量:counting_semaphore
C++20才支持。
cpp
#include <iostream>
#include <thread>
#include <semaphore>
#include <vector>
std::counting_semaphore<2> sema(2); // 创建一个初始值为2的信号量
void worker1() {
sema.acquire(); // 等待信号量
std::cout << "线程开始!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "线程结束"<< std::endl;
sema.release();
}
void worker2() {
sema.acquire(); // 等待信号量
std::cout << "线程开始!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "线程结束" << std::endl;
sema.release();
}
int main() {
std::vector<std::thread> ths;
for (int i = 0;i < 10;i++) {
ths.emplace_back(std::thread(worker1));
ths.emplace_back(std::thread(worker2));
}
for (auto& th : ths) {
th.join();
}
return 0;
}
线程开始!线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程开始!
线程结束
线程结束
原子操作 std::atomic
x++是三个原则操作,t=x, t++,x=t。如果能统一成一个原子操作,则无需同步。
cpp
long long g_x = 0;
std::atomic<long long> g_y;
void fun() {
for (int i = 0; i < 300 * 1000 * 1000 ;i++) {
g_x++;
g_y++;
}
}
int main()
{
std::thread th1(fun);
std::thread th2(fun);
th1.join();
th2.join();
}
g_y正确,g_x错误。
cpp
long long tmp = 0;
g_y.compare_exchange_strong(tmp, 1);
//g_y=1,tmp=0;
g_y.compare_exchange_strong(tmp, 1);
//g_y=1,tmp=1
无锁链表
cpp
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>
#include <set>
#include <assert.h>
struct Node {
Node* next;
int d;
};
std::atomic<Node*> head = nullptr;
void Push(int x) {
Node* cur = new Node;
cur->d = x;
cur->next = head.load();
while (!head.compare_exchange_strong(cur->next, cur));
}
void Pushs(int begin) {
for (int i = 0;i < 1000;i++) {
{Push(begin + i);}
}
}
int main()
{
const int cnt=1000;
std::vector<std::thread> ths;
for (int i = 0; i < cnt; i++) {
ths.emplace_back(std::thread([&](int x) {Pushs(1000 * x);}
, i));
}
for (auto& th : ths) {
th.join();
}
std::set<int> s;
for (auto tmp = head.load(); tmp != nullptr; tmp = tmp->next) {
s.emplace(tmp->d);
}
assert(s.size()== cnt*1000);
assert(0 == *s.begin());
assert(1000 * cnt - 1 == *s.rbegin());
getchar();
}
std::call_once / std::once_flag
cpp
#include <mutex>
#include <iostream>
std::once_flag flag;
void init() {
// 初始化工作
std::cout << "初始化" << std::endl;
}
void thread_func() {
std::call_once(flag, init); // 多个线程同时调用,init 只执行一次
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
std::thread t3(thread_func);
t1.join(); t2.join(); t3.join();
}
异常后会重新执行
cpp
#include <mutex>
#include <iostream>
std::once_flag flag;
int cnt = 0;
void init() {
if (cnt++ < 2) {
throw("初始化失败");
}
// 初始化工作
std::cout << "初始化" << std::endl;
}
void thread_func() {
try
{
std::call_once(flag, init); // 多个线程同时调用,init 只执行一次
}
catch(...){
}
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
std::thread t3(thread_func);
t1.join(); t2.join(); t3.join();
}
Meyers Singleton(函数局部静态变量)
C++11及更高版本,保证局部静态变量的现场安全。
cpp
#include <mutex>
#include <iostream>
class CLog {
public:
static CLog Log() {
static CLog log;
return log;
}
CLog() {
std::cout << "构造函数";
}
};
void thread_func() {
CLog::Log();
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
std::thread t3(thread_func);
t1.join(); t2.join(); t3.join();
std::async
cpp
#include <mutex>
#include <iostream>
#include <future>
int main() {
auto fun = [] {
return 1;
};
auto fut = std::async(fun);
auto x = fut.get();
std::cout << x << std::endl;
}
会启动新线程,运行结果1。
自动处理结果
cpp
#include <mutex>
#include <iostream>
#include <future>
int main() {
for (int i = 0;i < 3;i++)
{
std::promise<int> prom;
auto fut = prom.get_future();
std::thread t([&prom,i] {
std::this_thread::sleep_for(std::chrono::seconds(i+1));
prom.set_value(18);
});
if (fut.wait_for(std::chrono::seconds(2)) == std::future_status::ready) {
std::cout << fut.get() << std::endl;
}
else {
std::cout << "超时" << std::endl;
}
t.join();
}
}
18
18
超时
扩展阅读
| 计算几何为骨,排样优化为魂 |
|---|
| 作品一:亲士套料工具箱 作品二: 亲士CAD工具箱 |
| 经典文章推荐:二维排样 |
| 万物皆数学 |
| 查阅鄙人的博文,请点击博文下载学院导航 |
| 活到老,学到老。明朝中后期,大约50%的进士能当上堂官(副部及更高);能当上堂官的举人只有十余人。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。
