// 同时对多个 mutex 上锁 std::mutex m1; std::mutex m2; //std::unique_lock<std::mutex> lock_a(m1, std::defer_lock); //std::unique_lock<std::mutex> lock_b(m2, std::defer_lock); // std::def_lock 留下未上锁的互斥量 //std::lock(lock_a, lock_b); // 互斥量在这里上锁, 并修改对象的上锁标志 std::lock(m1, m2); // 锁住两个互斥量 std::lock_guard<std::mutex> lock_a(m1, std::adopt_lock); // std::adopt_lock 参数表示对象已经上锁,因此不会调用 lock 函数 std::lock_guard<std::mutex> lock_b(m2, std::adopt_lock); std::call_once 函数模板
如果多个线程需要同时调用某个函数,std::call_once 可以保证多个线程对该函数只调用一次, 并且是线程安全的.
线程安全的延迟初始化-- 使用 std::call_once 和 std::once_flag
考虑下面的代码, 每个线程必须等待互斥量,以便确定数据源已经初始化, 这导致了线程资源产生不必要的序列化问题.
std::shared_ptr<some_resource> resource_ptr; std::mutex resource_mutex; void foo() { std::unique_lock<std::mutex> lk(resource_mutex); // 所有线程在此序列化 if (!resource_ptr) { resource_ptr.reset(new some_resource); // 只有初始化过程需要保护 } lk.unlock(); resource_ptr->do_something(); }
使用双重检查锁优化上述代码, 指针第一次读取数据不需要获取锁, 并且只有在指针为NULL时才需要获取锁; 然后, 当获取锁之后, 指针会被再次检查一遍(这就是双重检查的部分), 避免另一的线程在第一次检查后再做初始化, 并且让当前线程获取锁.
这样同样存在问题, 即潜在的条件竞争, 因为外部的读取锁①时没有与内部的写入锁进行同步③, 因此就会产生条件竞争,这个条件竞争不仅覆盖指针本身, 还会影响到其指向的对象:
即使一个线程知道另一个线程完成对指针进行写入, 它可能没有看到新创建的some_resource实例, 然后调用do_something()④后, 得到不正确的结果. 这在C++标准中被指定为“未定义行为”.
void undefined_behaviour_with_double_checked_locking() { if (!resource_ptr) // 1 { std::lock_guard<std::mutex> lk(resource_mutex); if (!resource_ptr) // 2 { resource_ptr.reset(new some_resource); // 3 } } resource_ptr->do_something(); // 4 }
C++的解决方法:
std::shared_ptr<some_resource> resource_ptr; std::once_flag resource_flag; // 1 void init_resource() { resource_ptr.reset(new some_resource); } void foo() { std::call_once(resource_flag,init_resource); // 可以完整的进行一次初始化 resource_ptr->do_something(); }
线程安全类成员的延迟初始化
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(connection_details_) {} void send_data(data_packet const& data) // 1 { std::call_once(connection_init_flag, &X::open_connection, this); // 2 connection.send_data(data); } data_packet receive_data() // 3 { std::call_once(connection_init_flag, &X::open_connection, this); // 2 return connection.receive_data(); } };
boost::shared_lock读者-写者锁 boost::shared_lock, 允许两中不同的使用方式:一个“作者”线程独占访问和共享访问, 让多个“读者”线程并发访问. (C++11标准不支持)
其性能依赖与参与其中的处理器数量, 也与读者和写者线程的负载有关. 一种典型的应用: