线程导致的内存问题
在线程中可能访问局部变量的引用或者地址,但是在执行过程中该变量会被释放,导致线程运行出错,比如下面的例子
struct func {
int& i_;//绑定外面的一个变量
func(int& i) :i_(i) {}
void operator()() {
for (int i = 0; i < 3; i++) {
i_ = i;
cout << "i= " << i_ << endl;
}
}
};
void oops() {
int local_val = 0;
func myfunc{ local_val };//mufunc绑定local_val
thread functhread(myfunc);//线程内部执行
functhread.detach();//线程本身分离到后台执行
}
int main()
{
//会有隐患,线程本身可能还在执行,但是oops可能运行完毕导致local_val的释放
//此时i_ = i;的修改就是有问题的
oops();
//this_thread::sleep_for(chrono::seconds(1));
//如果不进行睡眠,那么一个i的值都打印不出来
}
因为线程分离了,local_val就有可能被回收。直接的修改方式就是把detach改为join,这样只有等到线程使用完, local_val才会被回收。
thread_guard
对于一个线程如果不将它分离(detach),那么就要记得对它调用join。问题是并不是每次都会记得,甚至记得了也不一定能调用成功,比如下面的示例:
void do_something_in_this_thread();
void f() {
int local_val = 0;
func myfunc(local_val);
std::thread t1(myfunc);
do_something_in_this_thread();//此处出现异常
t1.join();//t1不会执行join,但是会析构,因此就无法保证线程任务完整执行
}
如果要确保会调用join,需要捕获异常
void do_something_in_this_thread();
void f() {
int local_val = 0;
func myfunc(local_val);
std::thread t1(myfunc);
try{do_something_in_this_thread();//此处出现异常
}
catch(exception &e){
t1.join();
}
t1.join();
}
这样非常麻烦,因为需要对所有可能出现异常的地方都使用try_catch。因此利用RAII的想法,设计一个类可以在析构的时候自动调用线程的join函数。
class thread_guard {
public:
//接收的是引用,因为该类的作用只是在析构时帮助线程调用join
explicit thread_guard(thread& t) :t_(t) {}
~thread_guard() {
if (t_.joinable())//join只能调用一次,如果其他地方调用过了,这里就不会再调用了
t_.join();
}
private:
std::thread& t_;
//不允许拷贝,避免多个thread_guard对象管理同一个线程
//因为joinable并非原子性的,多个thread_guard析构时可能多次调用join。所以禁止拷贝
thread_guard(const thread_guard&) = delete;
thread_guard& operator =(const thread_guard&) = delete;
};
下面的情况,虽然会出现异常,但是线程依旧会调用join,保证了线程的安全性。因为出现异常后,并没有捕获,因此直接执行局部变量的析构,在析构thread_guard对象时会让绑定的线程执行join。
void do_something_in_this_thread();
void f() {
int local_val = 0;
func myfunc(local_val);
std::thread t1(myfunc);
thread_guard guard1(t1);
do_something_in_this_thread();//此处出现异常
}
并行版的求和
#include <algorithm>
#include <numeric>
#include<thread>
#include <vector>
#include <functional>
//单使用线程是没办法获取返回值的,因此只能使用引用传回返回值
//写一个仿函数类,作为线程的任务
template<typename Iterator,typename T>
struct accumulate_block {
void operator()(Iterator start, Iterator end, T& result) {
result = std::accumulate(start, end,result);
}
};
template<typename Iterator, typename T>
T parallel_accumulate(Iterator start, Iterator end, T init) {
const auto len = std::distance(start, end);//计算长度
if (len <= 0) return init;//长度不符合,返回初始值
const unsigned long min_per_thread = 25;//线程至少处理25个
const unsigned long max_threads = (len + min_per_thread-1) / min_per_thread;//计算最大需要多少个线程数量
//如果hardware_threads返回0,可能表明现在不能获取系统信息
const unsigned long hardware_threads = std::thread::hardware_concurrency();//硬件支持的程序能启动多少个个线程
//如果hardware_threads为0,默认是2;线程数量是hardware_threads和max_threads的最小值
const unsigned long thread_num = std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);
const unsigned long block_size = len / thread_num;//计算出每个线程处理的大小
std::vector<T> results(thread_num);
std::vector<std::thread> threads(thread_num-1);//最后一个由主线程完成,因为最后一块不一定刚好是block_size
Iterator block_start = start;//当前位置
for (int i = 0; i < thread_num-1; i++) {
Iterator block_end = block_start;
std::advance(block_end, block_size);
threads[i] = std::thread(accumulate_block<Iterator, T>(), block_start, block_end, std::ref(results[i]));
block_start = block_end;
}
accumulate_block<Iterator, T>()(block_start, end, results[thread_num - 1]);//最后一块由主线程执行
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
//std::for_each(threads.begin(), threads.end(), [](thread &t){t.join()});这两效果是一样的
return std::accumulate(results.begin(), results.end(), init);
}
线程安全的栈
struct empty_stack :std::exception {
const char* what() const noexcept {}
};
template<typename T>
class threadsafe_stack {
public:
explicit threadsafe_stack() = default;
explicit threadsafe_stack(const threadsafe_stack& other) {//拷贝
lock_guard<mutex> lock(other.m);//给other上锁,避免拷贝时other被修改
data = other.data;
}
threadsafe_stack& operator=(const threadsafe_stack& other) = delete;
void push(T new_value) {
lock_guard<mutex> lock(m);
data.push(std::move(new_value));
}
/*T pop() {
lock_guard<mutex> lock(m);
if (data.empty()) throw empty_stack{};
T value = data.top();
data.pop();
return value;
}*/
shared_ptr<T> pop() {//用智能指针减少拷贝开销
lock_guard<mutex> lock(m);
if (data.empty()) return nullptr;
shared_ptr<T> res = make_shared<T>(data.top());
data.pop();
return res;
}
bool empty()const {
lock_guard<mutex> lock(m);
return data.empty();
}
private:
stack<T> data;//数据
mutable mutex m;//锁,mutable是为了在const成员函数中也能加锁
};
同时加锁;std::lock
同时加锁是可能的需求,在一般的操作系统的学习中,同时加锁的要求一般是通过外层再套一层锁来实现的。不过标准库给我们提供了安全的同时加锁的接口,可以保证不会死锁。
下面以一个类为例,它的数据很大,不能实现类的拷贝操作,只能通过交换来完成类的。
//同时加锁的案例
class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
class some_big_object {//这是一个很大的类,无法拷贝,只能交换
private:
int data;//假装是一个很大的类型
mutex m;
public:
some_big_object() = default;
some_big_object(const some_big_object&) = delete;
some_big_object& operator=(const some_big_object&) = delete;
friend void swap(some_big_object& lhs, some_big_object& rhs) {//交换
if (&lhs == &rhs) return;
std::lock(lhs.m, rhs.m);//std::lock可以同时加锁,参数可以不止两个
//采用lock_guard对锁进行自动释放
//adopt_lock表示m已经加锁了,lock_guard构造时不会再尝试加锁
lock_guard<mutex> lock_a(lhs.m, std::adopt_lock);
lock_guard<mutex> lock_b(rhs.m, std::adopt_lock);
//std::scoped_lock lock(lhs.m, rhs.m);
swap(lhs.data, rhs.data);
}
};
在c++17中,标准库提供了std::scoped_lock,用来代替std::lock,使用时参考注释代码,但是scoped_lock内部会自动帮忙构造lock_guard,用户不用再考虑解锁的操作。
层级锁
层级锁的作用是通过一个层数来决定锁的顺序。如果先锁一个底层级的锁,再锁一个高层级的锁,就会出现错误。
比如锁1的层级是1000,锁2的层级是500 。可以先锁锁1,再锁锁2 。或者锁锁2 。如果锁了锁2,再想锁锁1就会报错。
下面这段代码实现了基本的层级锁功能。建议阅读代码的时候先看私有成员。
//层级锁
class hierarchical_mutex {
public:
explicit hierarchical_mutex(unsigned long value) :_hierarchy_value(value), _previous_hierarchy_value(0) {}
//拷贝构造和赋值都不能用
hierarchical_mutex(const hierarchical_mutex&) = delete;
hierarchical_mutex& operator =(const hierarchical_mutex&) = delete;
void lock() {
//上锁,首先检测能否上锁
check_for_hierarchy_violation();
//上锁
_internal_mutex.lock();
//更新层级值
update_hierarchy_value();
}
void unlock() {
//解锁的时候,线程当前层级必须等于本锁的层级,保证解锁的是本锁
if (_this_thread_hierarchy_value != _hierarchy_value) {
throw std::logic_error("mutex hierarchy violated");
}
//线程层级恢复到上锁之前的情况
_this_thread_hierarchy_value = _previous_hierarchy_value;
_internal_mutex.unlock();
}
bool try_lock() {
check_for_hierarchy_violation();
//尝试解锁,用mutex自带的就行
if (!_internal_mutex.try_lock()) {
return false;
}
//执行到这的话代表上锁了,需要更新层级值
update_hierarchy_value();
return true;
}
private:
std::mutex _internal_mutex;
//本锁的层级值
const unsigned long _hierarchy_value;
//上一级的层级值
unsigned long _previous_hierarchy_value;
//本线程记录的层级值,thread_local 代表各个线程的_this_thread_hierarchy_value不同
static thread_local unsigned long _this_thread_hierarchy_value;
//_this_thread_hierarchy_value记录的线程当前的层级,只有_hierarchy_value小于线程当前层级才能上锁。
void check_for_hierarchy_violation() {
//检测是否能上锁
if (_this_thread_hierarchy_value <= _hierarchy_value) {//这种情况不能上锁,抛出异常
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value() {
//上锁后做的更新操作。用_previous_hierarchy_value 记录当前线程的层级,因为解锁的时候要恢复
_previous_hierarchy_value = _this_thread_hierarchy_value;
//当前线程的层级更改为本锁的层级
_this_thread_hierarchy_value = _hierarchy_value;
}
};
//初始化静态变量
thread_local unsigned long hierarchical_mutex::_this_thread_hierarchy_value = ULONG_MAX;
void test_hierarchy_lock() {
hierarchical_mutex hmtx1(1000);
hierarchical_mutex hmtx2(500);
std::thread t1([&hmtx1, &hmtx2]() {
hmtx1.lock();
hmtx2.lock();
hmtx2.unlock();
hmtx1.unlock();
});
//线程2是错误的,它不按照顺序上锁
std::thread t2([&hmtx1, &hmtx2]() {
hmtx2.lock();
hmtx1.lock();
hmtx1.unlock();
hmtx2.unlock();
});
t1.join();
t2.join();
}
unique_lock的使用
C++ 标准库中的 std::unique_lock 是一个功能更强、更灵活的互斥锁管理类,相比 std::lock_guard 有以下 核心区别 和 使用优势。
| 特性 | std::lock_guard | std::unique_lock |
|---|---|---|
| RAII 自动加解锁 | ✅ | ✅ |
| 手动 lock/unlock | ❌ | ✅ |
| 延迟加锁 | ❌ | ✅(使用 defer_lock) |
| 支持条件变量 | ❌(不直接支持) | ✅(可以传给 std::condition_variable) |
| 可移动(移动构造) | ❌ | ✅ |
| 空锁(无效锁) | ❌ | ✅(可构造为空) |
| 占用更多资源 | 否(轻量) | 是(更复杂) |
1. 常规用法(等价于 lock_guard)
std::mutex m;
void func() {
std::unique_lock<std::mutex> lock(m); // 构造时加锁
// 临界区
} // 析构自动解锁
2. 延迟加锁:std::defer_lock
std::mutex m;
void func() {
std::unique_lock<std::mutex> lock(m, std::defer_lock); // 不立即加锁
// ...
lock.lock(); // 手动加锁
// ...
lock.unlock(); // 手动解锁
}
3. lock/unlock 多次使用
std::unique_lock<std::mutex> lock(mtx);
do_something();
lock.unlock(); // 提前释放锁
do_noncritical_work();
lock.lock(); // 再次加锁
do_more();
4. 与 std::condition_variable 配合使用
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void wait_for_ready() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 自动解锁并等待,再加锁
}
cv.wait()要求unique_lock,因为它需要临时释放并重新获取锁,而lock_guard做不到。
5. 移动语义
std::unique_lock<std::mutex> a(mtx);
std::unique_lock<std::mutex> b = std::move(a); // 转移所有权
6.owns_lock
owns_lock() 函数说明
bool owns_lock() const noexcept;
功能:
返回 true 表示 unique_lock 当前拥有锁的所有权,否则返回 false。
应用场景:
-
延迟加锁后判断是否加锁成功。
-
条件变量等待之后判断锁是否仍然有效。
-
在复杂逻辑中安全调用
unlock()前做检查。
shared_lock的使用
std::shared_lock 是 C++14 引入的标准库类型,用于配合 std::shared_mutex(或 std::shared_timed_mutex)实现**多读单写锁(读写锁)**机制,是多线程同步中非常常用的轻量级工具。
std::shared_lock 是一个 RAII 风格的共享锁,允许多个线程同时读取数据,但写入必须独占。
使用背景:为什么需要 shared_lock?
在多线程程序中,读操作通常是安全的,多个线程并发读取没有冲突。如果使用 std::mutex,即使只是读,也只能一个线程访问。
std::shared_mutex + std::shared_lock 提供了一种机制:
-
多个线程可同时持有 共享锁(shared lock) → 多读
-
只能有一个线程持有 独占锁(unique lock) → 单写
-
共享锁和独占锁互斥,保证写时没有读、读时没有写
std::shared_lock 类定义(头文件 <shared_mutex>)
template<class Mutex> class shared_lock;
用法通常如下
#include <shared_mutex>
#include <shared_lock>
std::shared_mutex mtx; // 允许 shared/unique 两种锁
std::shared_lock<std::shared_mutex> lock(mtx); // 获取共享锁
常用构造方式
| 构造方式 | 含义 |
|---|---|
shared_lock(mutex) | 自动加锁(RAII) |
shared_lock(mutex, std::defer_lock) | 不加锁,手动调用 .lock() |
shared_lock(mutex, std::try_to_lock) | 尝试加锁,不阻塞 |
shared_lock(mutex, std::adopt_lock) | 接管你已经手动加锁的 mutex |
成员函数
| 函数名 | 功能描述 |
|---|---|
lock() | 手动加锁(若未加锁) |
try_lock() | 非阻塞尝试加锁,返回 true/false |
unlock() | 解锁 |
owns_lock() | 判断是否持有锁 |
mutex() | 获取底层 mutex 指针 |
release() | 放弃锁管理,返回 mutex 指针 |
示例 :DNS读写
#include<mutex>
#include <shared_mutex>
#include <thread>
#include <iostream>
#include <map>
using namespace std;
//shared_lock+shared_mutex
class DNSService {
public:
DNSService() {}//构造略
string QueryDNS(string dnsname) {//读操作,读可以共享
shared_lock<shared_mutex> shared_locks(_shared_mtx);//shared_lock与lock_guard/unique_lock不能共存,
//可以有多个shared_lock持有shared_mutex;但一旦有shared_lock持有shared_mutex,lock_guard/unique_lock就不能给shared_mutex加锁了,反之亦然
const auto it = _dns_info.find(dnsname);
if (it != _dns_info.end()) {//找到了
return it->second;//返回ip
}
return "";
}
void AddDNSinfo(string dnsname, string dnsentry) {
lock_guard<shared_mutex> guard_locks(_shared_mtx);//用unique_lock也可以;
_dns_info.insert(make_pair(dnsname, dnsentry));
}
private:
map<string, string> _dns_info;//dns信息,域名映射到ip
mutable shared_mutex _shared_mtx;
};
✅ 优势
-
高并发性能:多个读线程可并发访问,不阻塞。
-
RAII安全:自动管理加解锁,防止忘记 unlock。
-
灵活组合:支持
defer_lock、try_to_lock等策略。
⚠️ 注意事项
-
shared_lock只适用于std::shared_mutex或其派生类,不适用于普通std::mutex。 -
持有
shared_lock时不能升级为unique_lock(不能直接转为写锁)。 -
shared_lock与unique_lock是互斥的,不能共存。 -
别滥用共享锁,会导致写线程一直被“饿死”(写不进去)。
使用once_flag实现单例
#include <mutex>
#include <iostream>
#include<memory>
class Single {
private:
Single() = default;
Single(const Single&) = delete;
Single& operator=(const Single&) = delete;
static std::shared_ptr<Single> _instance;
public:
static std::shared_ptr<Single> GetInstance() {
static std::once_flag _flag;
std::call_once(_flag, [&]() {
_instance = std::shared_ptr<Single>(new Single{});
});
return _instance;
}
~Single() {
std::cout << "this is single destruct" << std::endl;
}
};
条件变量使用
让两个线程轮流打印1和2,使用条件变量来通知
condition_variable cva, cvb;
int num = 1;
void test_cv(){
std::thread t1([]() {
while (1) {
std::unique_lock<std::mutex> lock(mtx);
cva.wait(lock, []() {return num == 1; });
num++;
cout << "thread A print 1" << endl;
cvb.notify_one();
}
});
std::thread t2([]() {
while (1) {
std::unique_lock<std::mutex> lock(mtx);
cvb.wait(lock, []() {return num == 2; });
num--;
cout << "thread B print 2" << endl;
cva.notify_one();
}
});
t1.join();
t2.join();
}
线程安全的队列
同样用到了条件变量的技术
template<typename T>
class threadsafe_queue {
private:
mutable std::mutex mut;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue(){}
threadsafe_queue(const threadsafe_queue& other) {
std::lock_guard<std::mutex> lk(other.mut);
data_queue = other.data_queue;
}
void push(T new_value) {
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();//有可能其他线程消费队列,正在挂起等待数据,所以加入数据后notify一下
}
void wait_and_pop(T& value) {
//为空时会等待,通过引用把值传出去
std::unique_lock<std::mutex> lk(mut);
//判断队列是否为空,为空会挂起等待
data_cond.wait(lk, [this]() {return !data_queue.empty(); });
value = data_queue.front();
data_queue.pop();
}
std::shared_ptr<T> wait_and_pop() {
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]() {return !data_queue.empty(); });
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool try_pop(T& value) {
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty())
return false;
value = data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr<T> try_pop() {
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty()) {
return std::shared_ptr<T>();//返回空指针
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
}
bool empty() const {
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
future+async
std::async 用来启动一个异步任务,返回一个 std::future<T> 对象,通过它你可以在以后某个时刻获取该任务的返回值。
#include <iostream>
#include <future>
#include <string>
#include <thread>
#include <chrono>
using namespace std;
//定义一个异步任务
string fetchDataFromDB(string query) {
this_thread::sleep_for(chrono::seconds(5));
return "Data:" + query;
}
void use_async() {
future<string> resultFromDB = async(launch::async, fetchDataFromDB, "Data");
cout << "Doing something else" << endl;
string dbData = resultFromDB.get();
cout << dbData << endl;
}
std::async
函数模板,启动一个异步任务,有两种策略:
std::async(std::launch::async, func, args...) // 立即在新线程执行
std::async(std::launch::deferred, func, args...) // 推迟到 future.get()或wait() 调用时在当前线程执行
不指定策略(如:std::async(func, args...))时,编译器可能使用任何策略(包括同步执行)——不推荐依赖默认行为。
std::future<T>
-
是一个延迟计算(deferred computation)的结果容器
-
用
.get()获取异步任务的返回值(会阻塞直到任务完成) -
.valid()检查 future 是否有效 -
.wait()可以阻塞等待完成但不获取值
注意事项
| 问题 | 说明 |
|---|---|
.get() 只能调用一次 |
调用后 future 失效,不能再 get 第二次 |
.wait() 可调用多次 | 不会消耗future的状态 |
| 可能同步执行 | 如果没有指定 std::launch::async,任务可能不会异步执行 |
| 自动管理线程 | 不需要你手动创建或 join 线程,但线程何时结束不明确 |
| 无法取消 | 标准库的 std::future 无法取消任务(不像一些线程库那样) |
std::future 的 wait_for 和 wait_until 是非阻塞等待的一种方式,用于在获取结果前判断异步任务是否完成,而不是一味地 get() 阻塞等结果。
| 方法 | 含义 |
|---|---|
.get() | 阻塞直到结果可用 |
.wait() | 阻塞直到就绪,但不取结果 |
.wait_for() | 等待指定时间;判断是否完成 |
.wait_until() | 等待直到某个时间点;判断是否完成 |
int slow_task() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
}
int main() {
std::future<int> fut = std::async(std::launch::async, slow_task);
// 等待最多2秒
if (fut.wait_for(std::chrono::seconds(2)) == std::future_status::ready) {
std::cout << "Result: " << fut.get() << std::endl;
} else {
std::cout << "Timeout! Still working..." << std::endl;
}
// 再等待直到完成
fut.wait();
std::cout << "Final Result: " << fut.get() << std::endl;
}
状态返回值(std::future_status)
enum class future_status {
ready, // 异步结果已就绪
timeout, // 等待超时,结果未就绪
deferred // 异步调用是deferred策略(延迟执行)
};
wait_for vs wait_until
| 方法 | 参数类型 | 示例 |
|---|---|---|
wait_for | std::chrono::duration | fut.wait_for(3s) |
wait_until | std::chrono::time_point | fut.wait_until(steady_clock::now() + 3s) |
效果类似,wait_for 更适合“等多久”,wait_until 更适合“等到什么时候”**。
如果在局部使用async,会阻塞到任务结束
package_task使用实例
int my_task() {
std::this_thread::sleep_for(std::chrono::seconds(5));
cout << "my task run 5s";
return 42;
}
void use_package() {
//int()是my_task的函数类型
std::packaged_task<int()> task(my_task);
//先取得task的future
std::future<int> result = task.get_future();
//只能用move,task是一个可调用对象,线程可以执行
std::thread t(std::move(task));
t.detach();
//等待任务完成并获取结果,get会阻塞直到子线程工作完成
int value = result.get();
cout << "the result is:" << value << endl;
}
promise使用示例
void set_value(std::promise<int> prom) {
std::this_thread::sleep_for(std::chrono::seconds(2));
//和package_task不一样的是,promise不用等整个线程结束,set_value后就可以通过get获取
prom.set_value(10);
std::cout << "promise set value sucess" << endl;
}
void use_promise() {
//int是到时候要set_value时值的类型
std::promise<int> prom;
std::future<int> fut = prom.get_future();
//也是move
std::thread t(set_value, std::move(prom));
cout << "waiting for the thread to set the value...\n";
cout << "Value set by the thread:" << fut.get() << endl;
t.join();
}
还可以在子线程发生异常但是去主线程处理
void set_exception(std::promise<int> prom) {
try {
throw std::runtime_error("An error occured!");;
}
catch (...) {
prom.set_exception(std::current_exception());//设置异常
}
}
void use_promise_exception() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(set_exception, std::move(prom));
try {
std::cout << "Waiting for the thread to set the exception...\n";
fut.get();
}
catch (const std::exception& e) {
std::cout << "Exception by the thread" << e.what() << endl;
}
t.join();
}
shared_future的示例
void myFunction(std::promise<int>&& promise) {
std::this_thread::sleep_for(std::chrono::seconds(1));
promise.set_value(42);
}
void threadFunction(std::shared_future<int> future) {
try {
int result = future.get();
std::cout << "Result" << result << endl;
}
catch (const std::future_error& e) {
std::cout << "Future error:" << e.what() << endl;
}
}
void use_shared_future() {
std::promise<int> promise;
//get_future得到的普通future,这里有隐式的转换
std::shared_future<int> future = promise.get_future();
std::thread my_thread1(myFunction, std::move(promise));
//注意,shared_future是可以拷贝构造的,这里不能使用move。但是future是不可以拷贝的
std::thread my_thread2(threadFunction, future);
std::thread my_thread3(threadFunction, future);
//2,3线程获得的值都是一样的42
my_thread1.join();
my_thread2.join();
my_thread3.join();
}
future捕获异常
void may_throw() {
throw std::runtime_error("Oops,something went wrong!");
}
void use_future_exception() {
std::future<void> result = std::async(std::launch::async, may_throw);
try {
result.get();
}
catch (const std::exception& e) {
cerr << "Caught exception" << e.what() << endl;
}
}
线程池
一个线程池的小例子
#pragma once
#include<atomic>
#include<condition_variable>
#include<future>
#include<iostream>
#include<mutex>
#include<queue>
#include<thread>
#include<vector>
class ThreadPool {
public:
//线程池使用单例模式,所以拷贝构造和拷贝赋值删除,构造函数设为私有
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
static ThreadPool& instance() {
static ThreadPool ins;
return ins;
}
//Task 是std::packaged_task<void()>的别名,注意函数类型是无返回值无参数的
using Task = std::packaged_task<void()>;
//析构
~ThreadPool() { stop(); }
//首先使用了函数返回类型的推断,返回类型需要知道f这个可调用对象的返回类型,所以一开始的返回类型先写个auto。
//最后返回的是一个future
template<class F,class ...Args>
auto commit(F&& f, Args&&... args)->std::future<decltype(f(args...))> {
//RetTpye是f的返回类型的别名
using RetType = decltype(f(args...));
//如果停止,就返回一个空对象
if (stop_.load()) return std::future<RetType>{};
//task是一个智能指针,指向一个packaged_task对象,它的模板参数是一个函数类型,返回值是RetType,参数为空。
//但是f是可能有参数的,使用bind让f和参数绑定生成一个无参数的可调用对象,这样就符合packaged_task指定的类型
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
//需要提前获取packaged_task的future,因为之后要把它传给线程
std::future<RetType> ret = task->get_future();
{
//要往任务队列里加添加任务,给队列上锁
std::lock_guard<std::mutex> cv_mt(cv_mt_);
//我们任务队列的类型是Task,是packaged_task<void()>,可是task智能指针指向的是有返回值的,利用lambda表达式
//捕获task,并在里面执行内部的可调用对象。同时这个lambda表达式是无返回值无参数的,满足任务队列的需求。
//用emplace会根据传入的lambda表达式自动构造一个packaged_task<void()>的对象
tasks_.emplace([task]() {(*task)(); });
}
//队列里有任务了,可能有线程之前挂起等待任务,所以用条件变量去通知挂起的线程。
cv_lock_.notify_one();
return ret;
}
int idleThreadCount() {
return thread_num_;
}
private:
ThreadPool(unsigned int num = 5) :stop_(false) {
if (num < 1) thread_num_ = 1;
else thread_num_ = num;
start();
}
void start() {
for (int i = 0; i < thread_num_; i++) {
//往线程池里添加线程,每个线程都会等待任务队列给它发通知去取走任务执行
pool_.emplace_back([this]() {
//如果线程池没有停止
while (!this->stop_.load()) {
Task task;
{
//先上锁,记得要用unique_lock
std::unique_lock<std::mutex> cv_mt(cv_mt_);
//条件变量等待有任务进来或者是线程池停止,等待时挂起同时解锁cv_mt_
this->cv_lock_.wait(cv_mt, [this]() {
return this->stop_.load() || !this->tasks_.empty();
});
//虽然是不等了,但是还要再检查一次
if (this->tasks_.empty()) return;
//任务队列不为空,取走第一个
task = std::move(this->tasks_.front());
//任务队列弹出被取走的那个
this->tasks_.pop();
}
//线程数量减一
this->thread_num_.fetch_sub(1);
//执行任务
task();
//线程数量加一
this->thread_num_.fetch_add(1);
}
});
}
}
void stop() {
//停止 stop为true
stop_.store(true);
//通知所有挂起等待的线程
cv_lock_.notify_all();
//等待线程完成
for (auto& td : pool_) {
if (td.joinable()) {
std::cout << "join thread" << td.get_id() << std::endl;
td.join();
}
}
}
private:
std::mutex cv_mt_;
std::condition_variable cv_lock_;
//原子变量,保证操作是原子级的
std::atomic_bool stop_;
std::atomic_int thread_num_;
//任务队列
std::queue<Task> tasks_;
//线程池,里面有很多线程
std::vector<std::thread> pool_;
};
可以用下面的来测试一下
int main()
{
//注意两次m传入的不同,第二次使用std::ref外部的m才会被修改
int m = 0;
ThreadPool::instance().commit([](int& m) {
m = 1024;
std::cout << "inner set m is" << m << std::endl;
},m);
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "outer m is " << m << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
ThreadPool::instance().commit([](int& m) {
m = 1024;
std::cout << "inner set m is" << m << std::endl;
}, std::ref(m));
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "outer m is " << m << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
auto f=ThreadPool::instance().commit([](int a,int b)->int {
return a + b;
}, 1,6);
cout<<"thread calculate: "<<f.get()<<endl;
}
函数式编程的快速排序
template<typename T>
std::list<T> sequential_quick_sort(std::list<T> input) {
//如果输入为空,就直接返回
if (input.empty()) {
return input;
}
std::list<T> result;
//一般的快速排序都有一个基准元素,这里取input的第一个元素为基准元素
//并把它移动到result的开头。注意splice之后input就少了那个基准元素,而result的开头即为基准元素
//splice为list的一个函数,提供链表的移动功能
result.splice(result.begin(), input, input.begin());
//获得基准元素
const T& pivot = *result.begin();
//使用标准库的partition,把input剩下的元素根据基准元素分割
//divide_point指向的是第一个大于等于pivot的位置
auto divide_point = std::partition(input.begin(), input.end(), [&pivot](const T& t) {return t < pivot; });
std::list<T> lower_part;
//[input.begin(),divide_point)这个区间的元素都是小于pivot的
//把它们移动到新的链表里
lower_part.splice(lower_part.end(), input, input.begin(),divide_point);
//递归对新的链表进行快速排序
auto new_lower(sequential_quick_sort(std::move(lower_part)));
//input里剩下的元素都是大于等于pivot的,也递归对剩下的链表排序
auto new_higher(sequential_quick_sort(std::move(input)));
//注意这时候result的第一个元素是基准元素
//在基准元素后面添加的是比它大的new_higher链表
result.splice(result.end(), new_higher);
//比基准元素小的链表new_lower添加在result前面,这样刚好基准元素在中间
result.splice(result.begin(), new_lower);
return result;
}
可以用下面代码测试
std::list<int> numlists = { 6,1,0,7,5,2,9,-1 };
auto sort_result = sequential_quick_sort(numlists);
std::cout << "sorted result is ";
for (auto iter = sort_result.begin(); iter != sort_result.end(); iter++) {
std::cout << " " << (*iter);
}
std::cout << std::endl;
并行方式的快速排序
template<typename T>
std::list<T> parallel_quick_sort(std::list<T> input)
{
//前面都差不多
if (input.empty())
{
return input;
}
std::list<T> result;
result.splice(result.begin(), input, input.begin());
T const& pivot = *result.begin();
auto divide_point = std::partition(input.begin(), input.end(),
[&](T const& t) {return t < pivot; });
std::list<T> lower_part;
lower_part.splice(lower_part.end(), input, input.begin(),
divide_point);
//因为lower_part是副本,所以并行操作不会引发逻辑错误,这里可以启动future做排序
std::future<std::list<T>> new_lower = std::async(parallel_quick_sort<T>, std::move(lower_part));
auto new_higher(parallel_quick_sort(std::move(input)));
result.splice(result.end(), new_higher);
//注意要用get获取future的结果
result.splice(result.begin(), new_lower.get());
return result;
}
使用线程池的并行快排
//线程池就是上面写的
template<typename T>
std::list<T> parallel_quick_sort(std::list<T> input)
{
if (input.empty())
{
return input;
}
std::list<T> result;
result.splice(result.begin(), input, input.begin());
T const& pivot = *result.begin();
auto divide_point = std::partition(input.begin(), input.end(),
[&](T const& t) {return t < pivot; });
std::list<T> lower_part;
lower_part.splice(lower_part.end(), input, input.begin(),
divide_point);
//就是这里改了一下
std::future<std::list<T>> new_lower = ThreadPool::instance().commit(parallel_quick_sort<T>, std::move(lower_part));
auto new_higher(parallel_quick_sort(std::move(input)));
result.splice(result.end(), new_higher);
result.splice(result.begin(), new_lower.get());
return result;
}
Actor设计模式
actor设计模式的思想的每个actor类内部有一个任务队列,只有一个单线程从队列里取任务执行。向队列里投递任务是可以并发的
#include<iostream>
#include <functional>
#include<thread>
#include<mutex>
#include<queue>
#include<atomic>
class Actor {
using Message = std::function<void()>;
public:
Actor():stop_(false),worker_([this](){
while (!stop_) {
Message msg;
{
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this]() {return this->stop_ || !this->queue_.empty(); });
if (stop_) return;//如果停止了,队列里剩下的任务会被丢弃
msg= std::move(queue_.front());
queue_.pop();
}
msg();//处理任务
}
}) {}
~Actor() {
Stop();
}
template<typename Callable,typename ...Args>
void GetMessage(Callable &&f,Args &&...args) {
if (stop_) {
std::cout << "this actor has stopped!" << std::endl;
return;
}
{
std::lock_guard<std::mutex> lk(mtx_);
queue_.emplace(std::bind(std::forward<Callable>(f), std::forward<Args>(args)...));//采用bind让可调用对象符合Message类型
}
cv_.notify_one();
}
private:
std::mutex mtx_;
std::queue<Message> queue_;//任务队列
std::thread worker_;//单线程取任务处理
std::condition_variable cv_;
std::atomic<bool> stop_;
void Stop() {//析构时调用停止
stop_ = true;
cv_.notify_all();
if (worker_.joinable())
worker_.join();
}
};
也可以修改一下,析构时会将任务队列剩下的处理完再释放
#pragma once
#include <iostream>
#include <functional>
#include <thread>
#include <mutex>
#include <queue>
#include <atomic>
#include <condition_variable>
class Actor {
using Message = std::function<void()>;
public:
Actor() : stop_(false), worker_([this]() {
while (true) {//①
Message msg;
{
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [this]() { return stop_ || !queue_.empty(); });
// 优雅退出条件:停止 且 队列已空
if (stop_ && queue_.empty()) {//②
return;
}
// 队列非空,取出一个任务
msg = std::move(queue_.front());
queue_.pop();
}
msg(); // 执行任务
}
}) {}
~Actor() {
Stop();
}
// 向 Actor 投递一个消息(任务)
template<typename Callable, typename... Args>
void GetMessage(Callable&& f, Args&&... args) {
if (stop_) {
std::cout << "this actor has stopped!" << std::endl;
return;
}
{
std::lock_guard<std::mutex> lk(mtx_);
queue_.emplace(std::bind(std::forward<Callable>(f), std::forward<Args>(args)...));
}
cv_.notify_one();
}
private:
std::mutex mtx_;
std::queue<Message> queue_;
std::thread worker_;
std::condition_variable cv_;
std::atomic<bool> stop_;
void Stop() {
{
std::lock_guard<std::mutex> lk(mtx_);
stop_ = true;
}
cv_.notify_all();
if (worker_.joinable()) {
worker_.join();
}
}
};
①和②是和基础版不一样的地方
也可以做一个send函数,将任务指定的发送到另一个actor对象。这里就不展示了
csp设计模式
#include<queue>
#include<mutex>
#include<condition_variable>
template<typename T>
class Channel {
std::queue<T> queue;
std::mutex mtx_;
std::condition_variable cv_producer_;//往channel中放数据的生产者。如果队列满了让生产者挂起
std::condition_variable cv_consumer_;//消费者,队列为空就挂起
size_t capacity_;//最大容量
bool closed_ = false;//是否关闭
public:
//容量可以为0,表示没有缓冲
Channel(size_t capacity=0):capacity_(capacity){}
bool send(T value) {
std::unique_lock<std::mutex> lock(mtx_);
cv_producer_.wait(lock, [this]() {
//退出的条件
//1.容量为0,且队列为空,意思是channel无缓冲,那自然要让生产者继续
//2.容量不为0且队列还没到最大容量
//3.channel 关闭了,需要退出
return (capacity_ == 0 && queue.empty()) || queue.size()<capacity_ || closed_;
});
if (closed_) return false;//如果close_,直接返回false
queue.push(value);
cv_consumer_.notify_one();//通知消费者去取
return true;
}
bool receive(T& value) {
std::unique_lock<std::mutex> lock(mtx_);
cv_consumer_.wait(lock, [this]() {
//如果队列不为空,或者channel关闭,才继续,否则挂起
return !queue.empty() || closed_;
});
if (closed_ && queue.empty())
return false;
value = queue.front();
queue.pop();
cv_producer_.notify_one();//消费了一个,去通知生产者检查是否能继续生产
return true;
}
void close() {
std::unique_lock<std::mutex> lock(mtx_);
closed_ = true;
cv_consumer_.notify_all();
cv_producer_.notify_all();//需要通知所有等待的线程
}
};
可以用下列代码测试
Channel<int> ch(0);
std::thread producer([&]() {
for (int i = 0; i < 5; i++) {
ch.send(i);
std::cout << "Sent " << i << std::endl;
}
ch.close();
});
std::thread consumer([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(500));//故意延迟消费者开始消费
int val;
while (ch.receive(val)) {
std::cout << "Received:" << val << std::endl;
}
});
producer.join();
consumer.join();
joining_thread
如果简单的线程移动赋值,就会报错,比如
t1 = std::move(t2);//这两线程都初始化好了,都有自己的任务
joining_thread类就是一个移动赋值安全的类。
#include<thread>
class joining_thread {
private:
std::thread t_;//里面封装了一个线程
public:
//模板构造函数,仿照thread的构造函数,提供一个可执行对象和若干参数,记得要用完美转发
template<typename Callable,typename ...Args>
explicit joining_thread(Callable && func,Args&& ...args):
t_(std::forward<Callable>(func),std::forward<Args>(args)){}
//explicit防止普通线程隐式的转化成joining线程
explicit joining_thread(std::thread t)noexcept:t_(std::move(t)) {}
joining_thread(joining_thread &&other)noexcept:t_(std::move(other.t_)){}
//根据thread实现了joinable和join
bool joinable() const noexcept {
return t_.joinable();
}
void join() {
t_.join();
}
//最主要的是这个运算符重载,如果本线程还在执行,就等待线程执行完再移动赋值
joining_thread& operator=(joining_thread&& other)noexcept {
if (joinable()) {
join();
}
this->t_ = std::move(other.t_);
return *this;
}
~joining_thread() noexcept {
if (joinable()) {
join();
}
}
};
至于为什么普通的线程移动赋值就会报错,可看源码
这是线程移动赋值的源码,再去terminate里看一下
thread& operator=(thread&& _Other) noexcept {
if (joinable()) {
_STD terminate();
}
_Thr = _STD exchange(_Other._Thr, {});
return *this;
}
可以看到这个函数抛出异常,所以程序会出错
_ACRTIMP __declspec(noreturn) void __cdecl terminate() throw();
内存序
在 C++ 中,std::atomic 提供了 六种内存序(memory order),它们用于指定 不同线程之间对共享原子变量操作的可见性和执行顺序,从而确保线程安全。
| 内存序 | 是否保证顺序 | 线程间可见性 | 用途 |
|---|---|---|---|
| memory_order_relaxed | ❌ | ❌ | 高性能但无同步 |
| memory_order_consume | ⚠️已弃用 | ✅(数据依赖) | 不推荐使用 |
| memory_order_acquire | ✅ load后语义 | ✅ | 从共享数据读取 |
| memory_order_release | ✅ store前语义 | ✅ | 写共享数据 |
| memory_order_acq_rel | ✅ | ✅ | 读写混合操作 |
| memory_order_seq_cst | ✅全局顺序 | ✅ | 安全默认选项 |
memory_order_acquire(获取模式)
作用:
-
保证在这个原子变量 load 之后的所有读写操作,不会被重排序到它之前。
-
类似于“读屏障”。
-
常用于从共享变量读取数据前,确保之前的初始化可见。
使用场景:
-
从共享状态读取值的线程,确保其看到写入线程初始化的内容。
memory_order_release(释放模式)
作用:
-
保证在这个原子变量 store 之前的所有读写操作,不会被重排序到它之后。
-
类似于“写屏障”。
使用场景:
-
写入共享变量后,确保其他线程读取它时能看到之前的写入。
memory_order_acq_rel(获取+释放模式)
作用:
-
结合了
acquire和release的效果。 -
常用于
read-modify-write操作,如fetch_add、compare_exchange等。
-
acquire 部分:保证读到的旧值是别的线程发布的最新值;
-
release 部分:保证这个写操作之后的其他写入不会被重排到之前(即这次修改对后续线程可见)。
换句话说:
acq_rel让这个 RMW 操作在整个同步链中“可读、可写、可传递”。
memory_order_seq_cst(顺序一致性,默认)
作用:
-
最强的顺序保证:所有原子操作以全局一致的顺序发生。
-
编译器和 CPU 不得对其乱序。
使用场景:
-
默认的首选选项,除非你确实需要优化。
为什么要用内存序?用relaxed为什么不可以?
编译器与 CPU 可能不按你写的顺序执行
编译器和 CPU 为了优化性能,会重排(reorder)指令执行顺序,只要这种重排在单线程语义上是等价的,它们就可以做。
int x = 0;
int y = 0;
void thread1() {
x = 1;
y = 2;
}
在单线程中,这两句执行顺序对结果没有影响,但编译器可以自由调换顺序(y = 2; x = 1;)来优化,比如寄存器分配、流水线等。
但是,在多线程场景下这种重排可能就造成了同步错误!
std::atomic<bool> flag{false};
int data = 0;
void writer() {
data = 42;
flag.store(true, std::memory_order_relaxed);
}
void reader() {
while (!flag.load(std::memory_order_relaxed));
std::cout << data << std::endl;
}
在很多平台上(比如 x86、ARM):
-
CPU/编译器可能把
flag = true提前到data = 42之前执行(写屏障弱); -
读线程读到
flag = true,但此时data可能还没写入完成或写入尚未对其他线程可见; -
导致读线程看到的是
flag == true && data == 0!
正确做法:用 release/acquire 建立同步
void writer() {
data = 42;
flag.store(true, std::memory_order_release); // 写侧使用 release
}
void reader() {
while (!flag.load(std::memory_order_acquire)); // 读侧使用 acquire
std::cout << data << std::endl;
}
atomic类常用操作
store(value, memory_order) 将 value 原子地写入变量
load(memory_order) 原子地读取值
operator= / operator T() 提供简洁语法糖;不推荐在并发场景中依赖这些默认操作,因为你不能指定 memory_order,它们等价于 memory_order_seq_cst(最强同步)
exchange(value, memory_order)交换旧值并返回旧值:
int old = a.exchange(20); // a 现在是 20,返回原值
compare_exchange_strong(expected, desired)
如果当前原子变量的值等于 expected,就将其设置为 desired,返回 true;
否则什么也不做,把原子变量的当前值写回 expected,返回 false。
数值类特化操作:
fetch_add(val) 加法操作并返回旧值
fetch_sub(val)减法操作
等等
atomic_flag
std::atomic_flag 是 C++ 标准库中最轻量级的原子类型,适用于实现自旋锁(spinlock)等极简单的同步机制。
| 特性 | 描述 |
|---|---|
| 存储值类型 | 只有一个布尔值(true/false) |
| 默认构造 | 默认为 false(未设置) |
| 操作函数 | 只有两个:test_and_set() 和 clear() |
| 可保证 lock-free | 所有平台上 100% lock-free |
初始化
std::atomic_flag flag = ATOMIC_FLAG_INIT; // 必须使用宏初始化
⚠️ 没有默认构造函数,不能用 atomic_flag flag{} 或 = false 初始化!
test_and_set
bool old = flag.test_and_set(); // 设置为 true,并返回旧值
作用:
-
原子地将 flag 设置为
true -
返回设置前的值(
true表示之前就已设置)
clear
flag.clear(); // 把 flag 设为 false
通常配合 test_and_set() 用于实现简单的锁释放。
和 std::atomic<bool> 有什么区别?
| 比较点 | atomic_flag | atomic<bool> |
|---|---|---|
| 接口数量 | 只有两个函数 | 完整的原子接口 |
| 语义简洁 | 专为 flag 设计 | 更通用 |
| lock-free 保证 | 所有平台都 lock-free | 不一定 lock-free |
| 应用场景 | 自旋锁、自定义锁结构 | 更复杂的并发状态判断 |
atomic_flag 是最简单、最快的原子类型,适合实现低层锁或 busy-wait 场景。你可以把它看作 C++ 标准提供的“最小锁原语”。
自旋锁
class SpinLock {
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
带锁的循环队列
#include <iostream>
#include <mutex>
#include <memory>
#include <cstdlib>
template<typename T, size_t Cap>
class CircularQueLK {
public:
//Cap是理想情况,但是循环队列里满的时候head与tail要空一个位置,所以_max_size要申请为Cap+1
CircularQueLK()
: _max_size(Cap + 1), _alloc(), _data(_alloc.allocate(_max_size)) {
}
CircularQueLK(const CircularQueLK&) = delete;
CircularQueLK& operator=(const CircularQueLK&) = delete;
CircularQueLK& operator=(const CircularQueLK&) volatile = delete;
~CircularQueLK() {
std::lock_guard<std::mutex> lock(_mtx);
while (_head != _tail) {
std::allocator_traits<decltype(_alloc)>::destroy(_alloc, _data + _head);
_head = (_head + 1) % _max_size;
}
_alloc.deallocate(_data, _max_size);
}
template<typename ...Args>
bool emplace(Args&& ...args) {
std::lock_guard<std::mutex> lock(_mtx);
if ((_tail + 1) % _max_size == _head) {//满了
std::cout << "circular que full!" << std::endl;
return false;
}
// 使用 allocator_traits 正确构造元素
std::allocator_traits<decltype(_alloc)>::construct(_alloc, _data + _tail,std::forward<Args>(args)...);
_tail = (_tail + 1) % _max_size;
return true;
}
bool push(const T& val) {
std::cout << "called push const T& version" << std::endl;
return this->emplace(val);
}
bool push(T&& val) {
std::cout << "called push T&& version" << std::endl;
return this->emplace(std::move(val));
}
bool pop(T& val) {
std::lock_guard<std::mutex> lock(_mtx);
if (_head == _tail) {
std::cout << "circular que empty!" << std::endl;
return false;
}
val = std::move(_data[_head]);
std::allocator_traits<decltype(_alloc)>::destroy(_alloc, _data + _head);
_head = (_head + 1) % _max_size;
return true;
}
private:
size_t _max_size;
std::allocator<T> _alloc;
T* _data;
std::mutex _mtx;
size_t _head = 0;
size_t _tail = 0;
};
使用 compare_exchange_strong 实现自旋锁
#include <atomic>
#include <thread>
#include <iostream>
class SpinLock {
public:
void lock() {
bool expected = false;
// 如果 _flag 是 false(未被占用),则设为 true(占用)
// 否则就一直忙等(spin)
//使用 memory_order_acquire 是为了确保在成功获得“锁”之后,后续所有对内存的访问不会被重排到 compare_exchange_strong 之前执行;
while (!_flag.compare_exchange_strong(expected, true, std::memory_order_acquire)) {
// 进入循环,及_flag被占用为true,expected会被设置为true
expected = false; // 重置 expected 为 false,才能继续比较
}
}
void unlock() {
_flag.store(false, std::memory_order_release);
}
private:
std::atomic<bool> _flag = false;
};
自旋锁版本的循环队列
#pragma once
#include <iostream>
#include <mutex>
#include <memory>
#include<atomic>
#include <cstdlib>
class SpinLock_exchange {
public:
void lock() {
bool expected = false;
// 如果 _flag 是 false(未被占用),则设为 true(占用)
// 否则就一直忙等(spin)
//使用 memory_order_acquire 是为了确保在成功获得“锁”之后,后续所有对内存的访问不会被重排到 compare_exchange_strong 之前执行;
while (!_flag.compare_exchange_strong(expected, true, std::memory_order_acquire)) {
// 进入循环,及_flag被占用为true,expected会被设置为true
expected = false; // 重置 expected 为 false,才能继续比较
}
}
void unlock() {
_flag.store(false, std::memory_order_release);
}
private:
std::atomic<bool> _flag = false;
};
// 基于 SpinLock 的线程安全循环队列
template<typename T, size_t Cap>
class CircularQueLK {
public:
CircularQueLK()
: _max_size(Cap + 1), _alloc(), _data(_alloc.allocate(_max_size)) {
}
CircularQueLK(const CircularQueLK&) = delete;
CircularQueLK& operator=(const CircularQueLK&) = delete;
CircularQueLK& operator=(const CircularQueLK&) volatile = delete;
~CircularQueLK() {
_mtx.lock();
while (_head != _tail) {
std::allocator_traits<decltype(_alloc)>::destroy(_alloc, _data + _head);
_head = (_head + 1) % _max_size;
}
_alloc.deallocate(_data, _max_size);
_mtx.unlock();
}
template<typename ...Args>
bool emplace(Args&& ...args) {
_mtx.lock();
bool success = false;
if ((_tail + 1) % _max_size == _head) {
std::cout << "circular que full!" << std::endl;
}
else {
std::allocator_traits<decltype(_alloc)>::construct(_alloc, _data + _tail, std::forward<Args>(args)...);
_tail = (_tail + 1) % _max_size;
success = true;
}
_mtx.unlock();
return success;
}
bool push(const T& val) {
std::cout << "called push const T& version" << std::endl;
return emplace(val);
}
bool push(T&& val) {
std::cout << "called push T&& version" << std::endl;
return emplace(std::move(val));
}
bool pop(T& val) {
_mtx.lock();
bool success = false;
if (_head == _tail) {
std::cout << "circular que empty!" << std::endl;
}
else {
val = std::move(_data[_head]);
std::allocator_traits<decltype(_alloc)>::destroy(_alloc, _data + _head);
_head = (_head + 1) % _max_size;
success = true;
}
_mtx.unlock();
return success;
}
private:
size_t _max_size;
std::allocator<T> _alloc;
T* _data;
SpinLock_exchange _mtx;
size_t _head = 0;
size_t _tail = 0;
};
循环依赖可见性失败
#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> x, y;
std::atomic<int> z;
void write_x()
{
x.store(true, std::memory_order_release); //1
}
void write_y()
{
y.store(true, std::memory_order_release); //2
}
void read_x_then_y()
{
while (!x.load(std::memory_order_acquire));
if (y.load(std::memory_order_acquire)) //3
++z;
}
void read_y_then_x()
{
while (!y.load(std::memory_order_acquire));
if (x.load(std::memory_order_acquire)) //4
++z;
}
void TestAR()
{
x = false;
y = false;
z = 0;
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load() != 0); //5
std::cout << "z value is " << z.load() << std::endl;
}
z == 0 是有可能的!
你需要理解 C++ 原子变量中的 acquire-release 语义的限制:
-
read_x_then_y()中的while (!x.load(acquire));只保证能看到x写入前的内容(即同步x.store(release)之前的操作),但它无法保证能看到y的值为 true,因为y是另一个变量、另一个 release-store。 -
同理,
read_y_then_x()中也一样。
所以,这两种情况是可能的:
-
read_x_then_y()成功读到x == true,但读到y == false,没有执行++z; -
read_y_then_x()成功读到y == true,但读到x == false,也没有执行++z。
换句话说:每个读线程都只看到部分更新,无法看到“完整世界”的状态。
这就导致:z 保持为 0,断言失败。
栅栏机制
void test_fence() {
atomic<bool> x = false, y = false;
int z = 0;
thread t1([&]() {
x.store(true, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);//保证之前的指令一定执行
y.store(true, std::memory_order_relaxed);
});
thread t2([&]() {
while (!y.load(std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);//效果由内存序决定
if (x.load(std::memory_order_relaxed))
z++;
});
t1.join();
t2.join();
}
std::atomic_thread_fence,它是 C++11 中为多线程提供的 显式内存栅栏函数,用于控制全局内存顺序,以补充原子变量的内存序语义。
void std::atomic_thread_fence(std::memory_order order) noexcept;
参数为内存序,功能即是内存序的效果;
线程安全的散列表
#pragma once
#include<iostream>
#include<shared_mutex>
#include<mutex>
#include<list>
#include<vector>
#include<string>
#include<memory>
#include<algorithm>
template<typename Key,typename Value,typename Hash=std::hash<Key>>
class threadsafe_lookup_table {
public:
threadsafe_lookup_table(
unsigned num_buckets = 19, Hash const& hasher_ = Hash()) :
buckets(num_buckets), hasher(hasher_)
{
for (unsigned i = 0; i < num_buckets; ++i)
{
buckets[i]= std::make_unique<bucket_type>();;
}
}
threadsafe_lookup_table(threadsafe_lookup_table const& other) = delete;
threadsafe_lookup_table& operator=(threadsafe_lookup_table const& other) = delete;
Value find(const Key& key) const {
return get_bucket(key).find(key);
}
void insert(const Key& key, const Value& val) {
return get_bucket(key).insert({ key,val });
}
void remove(const Key& key) {
return get_bucket(key).remove(key);
}
private:
class bucket_type {
friend class threadsafe_lookup_table;
using value_type = std::pair<Key, Value>;
using bucket_iterator =typename std::list<value_type>::iterator;
public:
void insert(const value_type& data) {
std::unique_lock<std::shared_mutex> lk(mtx);
bucket_iterator it = find_entry_for(data.first);
if (it == datas.end()) datas.push_back(data);
else it->second = data.second; // 更新已有 key 的 value
}
Value find(const Key& key) const{
std::shared_lock<std::shared_mutex> lk(mtx);
bucket_iterator it = find_entry_for(key);
if (it == datas.end()) return Value{};
else return it->second;
}
void remove(const Key& key) {
std::unique_lock<std::shared_mutex> lk(mtx);
bucket_iterator it = find_entry_for(key);
if (it == datas.end()) return;
else datas.erase(it);
}
~bucket_type() {
datas.clear();
}
private:
bucket_iterator find_entry_for(const Key& key) const{
return std::find_if(datas.begin(), datas.end(), [&](const value_type& data) {return data.first == key; });
}
std::list<value_type> datas;
mutable std::shared_mutex mtx;
};
std::vector<std::unique_ptr< bucket_type>> buckets;
//hash<Key>
Hash hasher;
bucket_type& get_bucket(const Key& key) const{
const std::size_t bucket_index = hasher(key)%buckets.size();
return *(buckets[bucket_index]);
}
};

1500

被折叠的 条评论
为什么被折叠?



