数据共享之相互排斥量mutex
相互排斥量介绍
相互排斥量能够保护某些代码仅仅能有一个线程运行这些代码。假设有个线程使用相互排斥量运行某些代码,其它线程訪问是会被堵塞。直到这个线程运行完这些代码,其它线程才干够运行。
一个线程在訪问共享数据前。给相互排斥量上锁,这时其它线程再给相互排斥量上锁会堵塞直到这个线程给相互排斥量解锁。
相互排斥量是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()对象。指针或引用使用不当回破坏保护的数据。查找迷途指针或引用比較easy,仅仅要在成员函数里不返回共享数据的指针或引用就能够。假设在进一步看,没那么简单。也可能会向你不能控制的函数传递共享数据的指针或引用,这些函数可能会把指针或引用存储起来。后面在用。这种话,相互排斥量就没法保护共享数据了。
#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);//使用相互排斥量保护data
func(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())// 线程1
if (!S.empty())// 线程2
int 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);在删除时。把值赋给value int 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的更多相关文章
- linux系统编程:线程同步-相互排斥量(mutex)
线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...
- 【C/C++多线程编程之六】pthread相互排斥量
多线程编程之线程同步相互排斥量 Pthread是 POSIX threads 的简称,是POSIX的线程标准. Pthread线程同步指多个线程协调地,有序地同步使用共享 ...
- Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁
相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...
- Linux多线程同步之相互排斥量和条件变量
1. 什么是相互排斥量 相互排斥量从本质上说是一把锁,在訪问共享资源前对相互排斥量进行加锁,在訪问完毕后释放相互排斥量上的锁. 对相互排斥量进行加锁以后,不论什么其它试图再次对相互排斥量加锁的线程将会 ...
- μCOS-II系统之事件(event)的使用规则及Semaphore的相互排斥量使用方法
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wavemcu/article/details/27798493 ****************** ...
- WinCE C#程序,控制启动时仅仅能启动一个程序,使用相互排斥量来实现,该实现方法測试通过
</pre><pre code_snippet_id="430174" snippet_file_name="blog_20140718_5_46349 ...
- Linux线程相互排斥量--进程共享属性
多线程中.在相互排斥量和 读写锁的 属性中.都有一个叫 进程共享属性 . 对于相互排斥量,查询和设置这个属性的方法为: pthread_mutexattr_getpshared pthread_mut ...
- 多线程相互排斥--mutex(二)
不知道大家对多线程或多进程间的同步相互排斥的控制机制了解的怎么样,事实上有非常多种方法能够实现这个目的,可是这些方法事实上由4种最主要的方法实现.这4种最主要的方法详细定义例如以下:在这有讲得不正确的 ...
- android NDK编程:使用posix多线程与mutex相互排斥同步
MainActivity.java 调用原生方法 posixThreads(int threads, int iterations) 启动线程 package com.apress.threads; ...
随机推荐
- POJ 2407
裸 的求欧拉函数 #include <iostream> #include <cstdio> #include <cstring> #include <alg ...
- 鸟哥Linux私房菜知识点总结3到5章
感觉自己对Linux的理解一直不够,所以近期翻看了一本<鸟哥的Linux私房菜>.这是一本基础的书,万丈高楼平地起,会的不多但能够学.这是我整理的一些知识点,尽管非常基础.希望和大家共同交 ...
- Android - 加入Android的OpenCV依赖库(Android Dependencies) 问题
加入Android的OpenCV依赖库(Android Dependencies) 问题 本文地址: http://blog.csdn.net/caroline_wendy 假设想要加入OpenCV的 ...
- SOA概念具体解释
1.概述 1.1基本定义 SOA(Service-Oriented Architecture)既面向服务的体系结构,是一个组件模型.它将应用程序猿的不同功能可是(称为服务)通过定义良好的接口联系起来. ...
- VC 6.0中添加库文件和头文件 【转】
本文转载自:http://blog.sina.com.cn/s/blog_9d3971af0102wxjq.html 加头文件包含 VC6.0中: VC6.0默认include包含路径:Tools&g ...
- webservice为什么不能用List参数,而只能用数组代替,我想是否因为List没有具体的类型信息,但用泛型的List(如:List<customer>)为什么也不行。如果用作参数的类中含有List<T>字段该如何处理?webservice参数是否支持
转自:https://social.microsoft.com/Forums/zh-CN/aded4301-b5f1-4aa6-aa46-16c46a60d05e/webservice20026201 ...
- python中黏包现象
#黏包:发送端发送数据,接收端不知道应如何去接收造成的一种数据混乱现象. #关于分包和黏包: #黏包:发送端发送两个字符串"hello"和"word",接收方却 ...
- (转)Django学习之 第四章:Django模板系统
前面的章节我们看到如何在视图中返回HTML,但是HTML是硬编码在Python代码中的 这会导致几个问题: 1.显然,任何页面的改动会牵扯到Python代码的改动 网站的设计改动会比Python代码改 ...
- CorelDRAW X6冰点价加推800套燃爆6月
CorelDRAW是迄今最具创意的图形设计程序,并获得所有行业的公认的知名工具.在刚刚结束的CorelDRAW X6限量活动之后,CorelDRAW官方继续加推800套CorelDRAW X6满足用户 ...
- 悦享双节,Guitar Pro也来凑份热闹!
光阴似箭,又是一个金秋的十月,祖国迎来了第68个生日,不同以往的是今年的中秋佳节与国庆假日重叠在一起了,这算不算是喜上加喜呢? 提到国庆人们的耳边总是会响起了一遍又一遍的国歌“起来,起来不愿做奴隶的人 ...