数据共享之互斥量mutex
来源:互联网 发布:js生产pdf 编辑:程序博客网 时间:2024/06/03 17:52
互斥量介绍
互斥量可以保护某些代码只能有一个线程运行这些代码。如果有个线程使用互斥量执行某些代码,其他线程访问是会被阻塞,直到这个线程执行完这些代码,其他线程才可以执行。
一个线程在访问共享数据前,给互斥量上锁,这时其他线程再给互斥量上锁会阻塞直到这个线程给互斥量解锁。
互斥量是C++中最常用的数据保护机制,但是它也不万能的。在编写代码时,合理的组织代码来避免资源竞争非常重要。使用互斥量可能会带来其他问题,比如死锁。
在C++中使用互斥量
创建互斥量使用mutex,给互斥量上锁使用函数lock(),给互斥量解锁使用unlock()函数。在实际应用中,不建议直接使用上锁、解锁函数,因为你必须记得每次上锁后要解锁,否则会造成死锁。在标准库中提供了lock_guard类模板,它使用了RAII(资源申请即初始化),在构造函数中上锁,在析构函数中解锁。
下面一个例子,给链表添加结点和查找链表中是否包含某个元素,在操作链表前都需要包含链表。
#include<mutex>//包含互斥量头文件#include<list>#include<algorithm>#include<thread>#include<iostream>#include<Windows.h>//两个全局变量,用于线程间共享std::list<int> some_list;std::mutex some_mutex;void add_to_list(int new_value){std::lock_guard<std::mutex> guard(some_mutex);//guard为局部变量,分配在栈上,超出作用域即调用析构函数some_list.push_back(new_value);Sleep(100);//100ms}bool list_cotains(int value_to_find){std::lock_guard<std::mutex> guard(some_mutex); return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end();}void fun1(){for (int i = 0; i <50; ++i){add_to_list(i);}}void fun2(){for (int i = 50; i < 100; ++i){add_to_list(i);}}int main(){std::thread t1(fun1);std::thread t2(fun2);t1.join();t2.join();for (std::list<int>::iterator it = some_list.begin(); it != some_list.end(); ++it)std::cout << *it << std::endl;system("pause");}
合理的编码来保护共享数据
保护共享数据,不是简单的在每个函数里加上lock_guard()对象。指针或引用使用不当回破坏保护的数据。查找迷途指针或引用比较容易,只要在成员函数里不返回共享数据的指针或引用就可以。如果在进一步看,没那么简单。也可能会向你不能控制的函数传递共享数据的指针或引用,这些函数可能会把指针或引用存储起来,后面在用。这样的话,互斥量就没法保护共享数据了。
#include<mutex>//包含互斥量头文件#include<thread>#include<iostream>class some_data{int a;public:some_data():a(10){}void do_something(){std::cout << a << std::endl;}};class data_wrapper{private:some_data data;std::mutex m;public:template<typename Function>void process_data(Function func){std::lock_guard<std::mutex> guard(m);//使用互斥量保护datafunc(data);//这个函数是可能是引用传递参数,把共享数据传到外面,危险}};some_data *unprotected;//把模板函数定义为引用传递void maliciout_function(some_data& protected_data){unprotected = &protected_data;//共享数据被传出来了,不再受互斥量保护}data_wrapper x;int main(){x.process_data(maliciout_function);unprotected->do_something();//这里在没有互斥量保护下使用共享数据了system("pause");}
在保护共享数据的作用域内,不要返回共享数据的指针、引用,也不要把共享数据作为参数传递给其他人提供的函数。
查找接口中的资源竞争
一些接口,对于单线程来说是没有资源竞争的,但是多个线程使用时就未必了。例如:
std::stack<int> S;void Func(){int len = S.size();for (int i = 0; i < len; ++i){S.pop();}}
在多线程环境下,先判断栈S大小,再处理。如果再处理过程中,其他线程给栈添加或删除元素,上面代码就会有问题。
单个接口是安全的,但是接口组合使用就未必了。假设栈的接口函数收保护,在某一时刻只能有一个运行。那么下面代码:
if (!S.empty())// 线程1if (!S.empty())// 线程2int const value = S.top();int const value = S.top();s.pop();s.pop();do_something(value);do_something(value);
这是线程1和线程2将去到相同的数据,但是两个线程都pop(),将会造成一个数据未被使用。解决这个问题的一个办法是把top()和pop()用互斥量保护起来。但是Tom Cargill指出,如果对象的复制构造函数在栈上且抛出异常,上面做法就会有问题。
假设把top和pop做成了原子操作,如果在top时,开辟空间失败,这是对象不能返回,而后面又执行了pop,这时元素在没有使用情况下被清除了。所以把top和pop做成两个接口还是有道理的。
有下面集中解决办法:
1、在pop时传递一个引用参数
//void pop(int &value);在删除时,把值赋给valueint k;S.pop(k)
2、使用无异常的复制构造函数
在C++11中,右值引用使得move construtor不抛出异常(即使复制构造函数抛出异常)。一个有效的办法是:限制栈中元素的类型,仅使用不抛出异常的数据类型。
这样做是安全的,但是不是理想的。即使在编译阶段就可以借助std::is_nothrow_copy_constructible和std::is_nothrow_move_constructible类型来检测复制构造函数或move constructor是否会抛出异常,但是许多用户自定义类型有复制构造函数,却没有move constructor。这样的话,这种类型就不能存储到线程安全的栈。
3、返回pop对象的指针
返回指针而不是返回值,因为指针可以安全的复制,不会有异常。缺点是返回指针要开辟内存、保证内存不会泄露。不过可以借助shared_ptr来解决。
4、使用1和2或者1和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();std::shared_ptr<T> const res(std::make_shared<T>(data.top()));data.top();return res;}void pop(T& value){std::lock_guard<std::mutex> lock(m);//先检查是否为空if (data.empty()) throw empty();value = data.top();data.top();}bool empty()const{std::lock_guard<std::mutex> lock(m);return data.emplace();}};
- 数据共享之互斥量mutex
- 多线程之互斥量mutex
- 多线程之Mutex(互斥量)
- Linux 互斥量Mutex 进程间共享
- 数据共享之ContentProvider
- 数据共享之properties
- 数据共享之死锁
- WatchKit 之 数据共享
- 线程同步之互斥量mutex的使用
- 线程同步之互斥量mutex的使用
- Linux线程同步之互斥量(mutex)
- Raw-OS源码分析之互斥量Mutex
- C++多线程同步之Mutex(互斥量)
- Linux线程同步之互斥量(mutex)
- 数据共享之SharedPreference共享参数文件
- 互斥量Mutex
- 互斥量Mutex
- Mutex(互斥量)
- 话外篇——关于学习
- String的'+'的性能及原理
- poj 2411(状态压缩DP) zoj 1100
- linux rw in maintenance mode/extended partition
- 三极管的电平转换及驱动电路分析
- 数据共享之互斥量mutex
- strcpy和memcpy的区别
- 单 火 线 供 电
- PCB设计资料:看到最后才知道是福利
- Java 接口和抽象类区别(转)
- 使用单向循环链表实现字典操作 INSERT、DELETE 和 SEARCH
- CentOS配置smaba与Windows共享文件
- 从java到C++ 面向对象(一)
- Shell 符号含义 单引号 双引号 反引号