用互斥保护共享数据
线程安全的栈容器类
#include
#include
#include
#include
struct empty_stack : std::exception
{
const char* what() const throw();
}
template
class threadsafe_stack
{
private:
std::stack data;
mutable std::mutex m;
public:
threadsafe_stack() {}
threadsafe_stack(const threadsafe_stack& other)
{
std::lock_guard lock(other.m);
data = other.data; // 在构造函数的函数体内进行复制操作
}
threadsafe_stack& operator=(const threadsafe_stack&) = delete;
void push(T new_value)
{
std::lock_guard lock(m);
data.push(std::move(new_value));
}
std::shared_ptr pop()
{
std::lock_guard lock(m);
if(data.empty()) throw empty_stack();
std::shared_ptr const res(std::make_shared(data.top()));
data.pop();
return res;
}
void pop(T& value)
{
std::lock_guard lock(m);
if(data.empty()) throw empty_stack();
value = data.top();
data.pop();
}
bool empty() const
{
std::lock_guard lock(m);
return data.empty();
}
}
防范死锁的建议通常是,始终按相同顺序对两个互斥加锁。
运用 std::lock() 函数和 std::lock_guard<> 类模板,进行内部数据的互换操作
class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
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 lock_a(lhs.m, std::adopt_lock);
std::lock_guard lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
}
// C++17 的写法:
void swap(X& lhs, X& rhs)
{
if(&lhs == &rhs)
return;
std::scoped_lock guard(lhs.m, rhs.m); // 类模板参数推断
swap(lhs.some_detail, rhs.some_detail);
}
防范死锁的补充准则
hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
hierarchical_mutex other_mutex(6000);
int do_low_level_stuff();
int low_level_func()
{
std::lock_guard lk(low_level_mutex);
return do_low_level_stuff();
}
void high_level_stuff(int some_param);
int high_level_func()
{
// 先对 high_level_mutex 加锁,随后调用 low_level_func(),这两步符合加锁规则,因为互斥 high_level_mutex 所在的层级 10000 高于 low_level_mutex 所在的层级 5000。
std::lock_guard lk(high_level_mutex);
return high_level_stuff(low_level_func());
}
void thread_a()
{
high_level_func();
}
void do_other_stuff();
void other_stuff()
{
high_level_func();
do_other_stuff();
}
void thread_b() // 线程 b 在运行期出错。
{
std::lock_guard lk(other_mutex);
other_stuff();
}
class hierarchical_mutex
{
std:mutex internal_mutex;
unsigned long const hierarchy_value;
unsigned long previous_hierarchy_value;
static thread_local unsigned long this_thread_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 = this_thread_hierarchy_value;
this_thread_hierarchy_value = hierarchy_value;
}
public:
explicit hierarchical_mutex(unsigned long value)
: hierarchy_value(value)
, previous_hierarchy_value(0)
{}
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();
if(!internal_mutex.try_lock())
return false;
update_hierarchy_value();
return true;
}
};
thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);
运用 std::unique_lock<> 灵活加锁
void swap(X& lhs, X& rhs)
{
if(&lhs == &rhs)
return;
// 实例 std::defer_lock 将互斥保留为无锁状态
std::unique_lock lock_a(lhs.m, std::defer_lock);
std::unique_lock lock_b(rhs.m, std::defer_lock);
std::lock(lock_a, lock_b); // 到这里才对互斥加锁
swap(lhs.some_detail, rhs.some_detail);
}
在不同作用域之间转移互斥归属权
std::unique_lock get_lock()
{
extern std::mutex some_mutex;
std::unique_lock lk(some_mutex);
prepare_data();
return lk;
}
void process_data()
{
std::unique_lock lk(get_lock());
do_something();
}
按适合的粒度加锁
在初始化过程中保护共享数据
令所有线程共同调用 std::call_once() 函数,从而确保在该调用返回时,指针初始化由其中某线程安全且唯一地完成(通过适合的同步机制)。必要的同步数据则由 std::once_flag 实例存储,每个 std::once_flag 实例对应一次不同的初始化。相比显式使用互斥,std::call_once() 函数的额外开销往往更低,特别是在初始化已经完成的情况下,所以如果功能符合需求就应优先使用。
// 用互斥实现线程安全的延迟初始化:
std::shared_ptr resource_ptr;
std::mutex resource_mutex;
void foo()
{
std::unique_lock lk(resource_mutex);
if(!resource_ptr)
{
resource_ptr.reset(new some_resource);
}
lk.unlock();
resource_ptr->do_something();
}
// std::once_flag 类和 std::call_once() 函数的方式:
std::shared_ptr resource_ptr;
std::once_flag resource_flag;
void init_resource()
{
resource_ptr.reset(new some_resource);
}
void foo()
{
std::call_once(resource_flag, init_resource); // 初始化函数准确地被唯一一次调用
resource_ptr->do_something();
}
利用 std::call_once() 函数对类 X 的数据成员实施线程安全的延迟初始化
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)
{
std::call_once(connection_init_flag, &X::open_connection, this);
}
data_packet receive_data()
{
std::call_once(connection_init_flag, &X::open_connection, this);
return connection.receive_data();
}
}
如果把局部变量声明成静态数据,那样便有可能让初始化过程出现条件竞争。根据 C++ 标准规定,只要控制流程第一次遇到静态数据的声明语句,变量即进行初始化。若多个线程同时调用同一函数,而它含有静态数据,则任意线程均可能首先到达其声明处,这就形成了条件竞争的隐患。C++11 解决了这个问题,规定初始化只会在某一线程上单独发生,在初始化完成之前,其他线程不会越过静态数据的声明而继续运行。
某些类的代码只需用到唯一一个全局实例,这种情形可用以下方法代替 std::call_once():
class my_class;
my_class& get_my_class_instance() // 线程安全的初始化,C++11 标准保证其正确性
{
static my_class instance;
return instance;
}
保护甚少更新的数据结构
递归加锁