《C++ 并发编程实战》一些示例代码段

线程导致的内存问题

在线程中可能访问局部变量的引用或者地址,但是在执行过程中该变量会被释放,导致线程运行出错,比如下面的例子

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_guardstd::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_locktry_to_lock 等策略。


⚠️ 注意事项

  1. shared_lock 只适用于 std::shared_mutex 或其派生类,不适用于普通 std::mutex

  2. 持有 shared_lock 时不能升级为 unique_lock(不能直接转为写锁)。

  3. shared_lockunique_lock 是互斥的,不能共存。

  4. 别滥用共享锁,会导致写线程一直被“饿死”(写不进去)。

使用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::futurewait_forwait_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_forstd::chrono::durationfut.wait_for(3s)
wait_untilstd::chrono::time_pointfut.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(获取+释放模式)

作用:

  • 结合了 acquirerelease 的效果。

  • 常用于 read-modify-write 操作,如 fetch_addcompare_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_flagatomic<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]);
	}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值