天天看点

1.4. C++并发(线程之间共享数据)

多线程之间共享数据经常会产生竞争条件,当竞争条件破坏不变量时会导致问题的产生。比如多个线程对同一数据的修改可能会导致未定义行为,多线程中某一行为需要获取多个锁时可能会造成死锁。解决竞争条件产生的问题的解决方案通常有两个,一个是修改数据结构的设计,被称为无锁编程,另一个是使用互斥元保护共享数据。

案例1. 利用互斥元保护列表之简单实现

#include <list>
#include <mutex>
#include <algorithm>
std::list<int> some_list;
std::mutex some_mutex;
void add_to_list(int new_value)
{
	std::lock_guard<std::mutex> guard(some_mutex);
	some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
	std::lock_guard<std::mutex> guard(some_mutex);
	return std::find(fome_list.begin(), some_list.end(), value_to_find) != some_list.end();
}
           

案列2. 避免在使用互斥元保护数据的时候,将数据的指针和引用传递到锁的范围之外。

class some_data;
class data_wrapper
{
private:
	some_data data;
	std::mutex m;
public:
	template<typename Function>
	void process_data(Function func)
	{
		std::lock_guard<std::mutex> l(m);
		func(data);                       //有风险,传递了受保护数据到保护范围之外
	}
};
some_data* unprotected;
void malicious_function(some_data& protected_data)
{
	unprotected = &protected_data;
}
data_wrapper x;
void foo()
{
	x.process_data(malicious_function);   //传递了一个恶意函数
	unprotected->do_something();          //对受保护数据进行了未受保护的访问
}
           

案列3. 一个线程安全栈定义

#include <exception>
#include <memory>
#include <mutex>
#include <stack>
struct empty_stack: std::exception
{
	const char* what() const throw();
};
template<typename T>
class threadsafe_stack
{
private:
	std::stack<T> data;
	mutable std::mutex m;
public:
	threadsafe_stack(){}
	threadsafe_stack(const threadsafe_stack& other)//拷贝构造函数
	{
		std::lock_guard<std::mutex> lock(other.m);
		data = other.data;
	}
	threadsafe_stack& operator = (const threadsafe_stack&) = delete; 
	void push(T new_value)
	{
		std::lock_guard<std::mutex> lock(m);
		data.push(new_value);
	}
	std::shared_ptr<T> pop()
	{
		std::lock_guard<std::mutex> lock(m);
		if(data.empty()) throw empty_stack();
		std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
		data.pop();
		return res;
	}
	void pop(T& value)
	{
		std:lock_guard<std::mutex> lock(m);
		if(data.empty()) throw empty_stack();
		value = data.top();
		data.pop();
	}
	bool empty() const
	{
		std::lock_guard<std::mutex> lock(m);
		return data.empty()
	}
};
           

案例4. 在竞争条件中经常会出现死锁,死锁较常出现在需要锁定两个或以上互斥元的情况中。常见的建议是:1:使用相同的顺序锁定这两个互斥元 2:避免嵌套锁 3:在持有锁时,避免调用用户提供的代码 4:使用锁层次

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<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);
	}
};
           

案例5. 使用层次锁以及层次锁的定义

获取高层次锁可以获取底层次锁,而获取低层次锁则不得获取高层次锁且会抛出异常

hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
int do_low_level_stuff();
int low_level_func()
{
	std::lock_guard<hierarchical_mutex> lk(low_level_mutex);
	return do_low_level_stuff();
}
void high_level_stuff(int some_param);
void high_level_func()
{
	std::lock_guard<hierarchical_mutex> lk(high_level_mutex);
	high_level_stuff(low_level_func());
}

//线程a
void thread_a()
{
	high_level_func();   //高层次锁下可以获取低层次锁
}

//线程b
hierarchical_mutex other_mutex(100);
void do_other_stuff()
{
	high_level_func();
	do_other_stuff();
}
void thread_b()
{
	std::lock_guard<hierarchical_mutex> 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()
	{
		this_thread_hierarchy_value = previous_hierarchy_value; //解锁的时候要还原当前线程的层次值
		internal_mutex.lock();
		update_hierarchy_value();
	}
	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); //静态成员变量需要初始化
           

案例6. 更灵活的RAII风格锁,std::unique_lock

std::lock_guard是比较简单的RAII风格锁,并且可以传入std::adopt_lock作为第二个参数,表示已经上锁,使用起来十分的方便。另外,还有更加灵活的锁,std::unique_lock可以主动进行上锁 lock()和解锁unlock(),也能使用std::defer_lock()表示互斥元在构造时未上锁。在拥有了灵活性的同时,损失了一些性能。主要体现在std::unique_lock实现了lock()、try_lock()和unlock(),他们会调用底层上互斥元的同名函数去做实际工作,并且其实例内部有一个标识来表示该实例是否拥有此互斥元,只有在拥有互斥元,其析构的时候才能调用unlock()。因此,其牺牲了性能以获取易用性和灵活性。

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::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);
	}
};
           

案例7. 在作用于之间转移锁的所有权

std::unique_lock是可移动但不可复制的,如果被转移实例是右值即临时量,那么可以自动进行转移(隐式),否则需要显式地进行转移

std::unique_lock<std::mutex> get_lock()
{
	extern std::mutex some_mutex;
	std::unique_lock<std::mutex> lk(some_mutex);
	prepare_data();
	return  lk;
}
void process_data()
{
	std::unique_lock<std::mutex> lk(gete_lock)); //右值,可以直接进行转移所有权
	do_something();
}