《C++ 并发编程实战》读书笔记(1)
在交换操作中使用std::lock()
和std::lock_guard
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class some_big_object;
class X { private: some_big_object some_detail; std::mutex m; public: X(some_big_object const& sd) : some_detail(sd) {}
friend void swap(X& lhs, X& rhs) { if(&lhs == &rhs) { return; } std::lock(lhs.m, rhs.m); std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock); std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock); swap(lhs.some_detail, rhs.some_detail); } };
|
避免死锁的提示
避免嵌套锁。
如果你已经持有一个锁,就别再获取其他锁, 原因很简单因为每个线程仅持有一个锁。如果需要获取多个锁,就使用std::lock()
这样单个动作来执行。
在持有锁时,避免调用用户提供的代码。
因为代码是程序员写的,你不知道它会做什么。如果用户提供的代码也在获取一个锁的话,可能导致死锁。
以固定的顺序获取锁
如果你绝对需要获取两个或更多的锁,并且不能以std::lock
的单个操作取得,次优的做法是在每个线程中以相同的顺序获取它们。
见下面例子定义的层级锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class hierarchical_mutex { public: explicit hierarchical_mutex(const unsigned long value) : hierarchy_value_(value), previous_hierarchy_value_(0) {}
void lock() { check_for_hierarchy_violation(); internal_mutex_.lock(); update_hierarchy_value(); }
void unlock() { this_thread_hierarchy_value_ = previous_hierarchy_value_; internal_mutex_.unlock(); }
bool try_lock() { check_for_hierarchy_violation(); if (!internal_mutex_.try_lock()) return false; update_hierarchy_value(); return true; }
private: std::mutex internal_mutex_;
unsigned long const hierarchy_value_; unsigned long previous_hierarchy_value_; inline static thread_local unsigned long this_thread_hierarchy_value_ = 0;
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_ = this_thread_hierarchy_value_; this_thread_hierarchy_value_ = hierarchy_value_; } };
|
在交换操作中使用std::lock()
和std::unique_lock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class some_big_object;
class X { private: some_big_object some_detail; std::mutex m; public: X(some_big_object const& sd) : some_detail(sd) {}
firend void swap(X& lhs, X& rhs) { if(&lhs == & rhs) return; std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock); std::unique_lock<std::mutex> lock_b(rhs.m, std::defer_lock); std::lock(lock_a, lock_b); swap(lhs.some_detail, rhs.some_detail); } };
|
使用std::call_once
的线程安全的类成员延迟初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class X { private: connection_info connection_details; connection_handle connection;
std::once_flag connection_init_flag;
void open_connection() { connection = connection_manager.open(connection_details); } public: X(connection_info const& connection_details_) : connection_details(conection_details_) { }
void send_data(data_packet const& data) { std::call_once(connection_init_flag, &X::open_connection, this); connection.send_data(data); }
data_packet receive_data() { std::call_once(connection_init_flag, &X::open_connection, this); return connection.receive_data(); } };
|
在c++11
中,初始化被定义在只发生在一个线程上,并且其他线程不可以继续直到初始化完成,所以竞争条件仅仅在于哪个线程会执行初始化,而不会有更多别的问题。对于需要单一全局实例的场合,这可以用作std::call_once
的替代品。
1 2 3 4 5
| class my_class; my_class& get_my_class_instance() { static my_class instance; return instance; };
|
使用std::condition_variable
等待数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| template<typename T> class threadsafe_queue{ private: mutable std::mutex mut; std::queue<T> data_queue; std::condition_variable data_cond; public: threadsafe_queue() = default; threadsafe_queue(threadsafe_queue const& 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(); }
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 empty() const { std::lock_guard<std::mutex> lk(mut); return data_queue.empty(); } };
|