《C++并发编程实战》笔记-第3章在线程间共享数据

用互斥保护共享数据

1)不得向锁所在的作用域之外传递指针和引用,指向受保护的共享数据,无论是通过函数返回值将它们保存到对外可见的内存,还是将它们作为参数传递给使用者提供的函数,否则保护失效。

2)接口固有的条件竞争
如stack里empty()和top(),top()和pop()
解决:将top()和pop()合起来,但之所以分割是怕复制过程抛异常而造成数据丢失,所以只要解决这一点即可。
1.传入引用,存储弹出的元素。
2.提供不抛异常的拷贝构造函数,或不抛异常的移动构造函数。
3.返回指针,指向弹出的元素。使用share_ptr不必亲自管理内存分配。
结合方法1和方法2,或方法1和方法3灵活构建代码。
此外在合起来的函数内检查是否空栈。
另外,只留拷贝构造函数,删除赋值运算符,从而保证互斥的锁定会横跨整个复制过程。
另外,使用mutable修饰mutex,才能在带const的函数里锁定互斥。
坑:自定义异常出现“1个无法解析的外部命令”,是因为没有实现。
解决错误:返回一个内容即可。

/*
线程安全的栈容器类
*/
struct empty_stack :exception
{
	const char* what() const throw()
	{
		return "stack is empty!";
	}
};

template<typename T>
class threadsafe_stack
{
	stack<T> data;
	mutable mutex m; 
public:
	threadsafe_stack(){}

	threadsafe_stack(const threadsafe_stack& other) 
	{
		scoped_lock lock(other.m);
		data = other.data;
	}

	threadsafe_stack& operator=(const threadsafe_stack&) = delete;

	void push(T new_data)
	{
		scoped_lock lock(m);
		data.push(move(new_data));
	}

	void pop(T & value)
	{
		scoped_lock lock(m);
		if (data.empty()) throw empty_stack();
		value = data.top();
		data.pop();
	}

	shared_ptr<T> pop()
	{
		scoped_lock lock(m);
		if (data.empty()) throw empty_stack();
		shared_ptr<T> const res(make_shared<T>(data.top()));
		data.pop();
		return res;
	}

	bool empty() const
	{
		scoped_lock lock(m);
		return data.empty();
	}

};

3)C++17引入了类模板参数推导,还引入了scoped_lock是增强版的lock_guard,它是可变参数模板,可以同时锁住多个互斥,从而防范死锁。

4)防范死锁准则:
1.避免嵌套锁:假如已经持有锁,就不要试图获取第二个锁。万一确有需要获取多个锁,应采用lock()函数或scoped_lock<>模板,一次获取全部锁来避免死锁。
2.一旦持锁,就须避免调用由用户提供的程序接口。
3.依从固定顺序获取锁。
4.按层级加锁:如果已经持有锁,那只能获取相对低层级的锁,从而限制代码行为。
具体做法是将层级的编号赋予对应层级的互斥,并记录各线程分别锁定了哪些互斥。
另外,层级编号使用thread_local修饰,即每个线程都有最后一次加锁操作所涉及的层级编号。
5.将准则推广到锁操作以外。 
在不同作用域之间转移互斥归属权,可使用unique_lock<>。因为unique_lock实例不占有与之关联的互斥,所以互斥归属权可以在多个unique_lock实例之间转移。
另外,unique_lock<>比lock_guard<>灵活,不需要时可手动解锁。
除非绝对必要,否则不得在持锁期间进行耗时操作,如等待IO完成或获取另一个锁(即便知道不会死锁)。

/*
层级互斥
自定义的互斥型别只要实现3个成员函数,lock(),unlock(),try_lock(),就满足互斥概念所需具备的操作。
层级编号使用thread_local修饰,即每个线程都有最后一次加锁操作所涉及的层级编号。
*/
class hierarchical_mutex
{
	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 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 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);

//(使用)层级防范死锁
hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);

int do_low_level_stuff() { return 1; }
int low_level_func()
{
	scoped_lock lock(low_level_mutex);
	return do_low_level_stuff();
}

void do_high_level_stuff(int some_param) { cout << some_param << endl; }
void high_level_func()
{
	scoped_lock lock(high_level_mutex);
	do_high_level_stuff(low_level_func());
}

5)实现线程安全的延迟初始化,用call_once()函数和once_flag类,而不要用互斥(会毫无必要地迫使多个线程循序运行),也不要用双重检验(可能会导致数据竞争)。

6)C++17提供新的互斥(也称读写互斥),shared_mutex和shared_timed_mutex,后者支持更多的操作,所以若无须进行额外操作,应选用shared_mutex,可能带来性能增益。
具有两种使用方式:
(更新操作)允许单独一个“写线程”进行完全排他的访问,可用lock_guard<>或unique_lock<>锁定。
(无须更新)允许多个“读线程”共享数据或并发访问,可用shared_lock<>锁定,即多个线程同时锁住同一个互斥。


/*
运用shared_mutex保护数据结构
*/
template<typename T>
class shared_vector
{
	vector<T> data;
	mutable shared_mutex m;
public:
    shared_vector(const vector<T>& _data):data(_data){}

	bool find_vector(T const& value) const
	{
		shared_lock lock(m);
		auto it = find(data.begin(), data.end(), value);
		return (it != data.end()) ? true : false;
	}

	void update_vector(T const& value)
	{
		scoped_lock lock(m);
		data.push_back(value);
	}
};

7)thread构造错误
1错误:出现“尝试引用已删除的函数”,是因为thread类删除了拷贝构造函数和赋值运算符。
解决:使用move。
2错误:出现“std::invoke”: 未找到匹配的重载函数”,是因为线程运行的函数有返回值,但没有处理。
解决:使用lambda传引用,保存返回值。
3错误:出现“<function-style-cast>”: 无法从“void”转换为“std::thread”,是因为线程运行的函数有参数,但构造错误。
解决:使用thread t(&shared_vector<int>::update_vector, &my_v,11);

8)递归加锁,recursive_mutex,但不推荐使用这种设计。
如:每个公有函数都要锁住互斥后才操作,此时有一公有函数需要调用另一公有函数,若用mutex重复加锁将导致未定义行为,拙劣设计直接改用recursive_mutex。
但更好的方法,根据这两个公有函数的共同部分,提取出一个新的私有函数,新函数由这两个公有函数调用,并在调用前各自对mutex成员加锁,故无须重复加锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值