相互排斥量介绍

相互排斥量能够保护某些代码仅仅能有一个线程运行这些代码。假设有个线程使用相互排斥量运行某些代码,其它线程訪问是会被堵塞。直到这个线程运行完这些代码,其它线程才干够运行。

一个线程在訪问共享数据前。给相互排斥量上锁,这时其它线程再给相互排斥量上锁会堵塞直到这个线程给相互排斥量解锁。

相互排斥量是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的更多相关文章

  1. linux系统编程:线程同步-相互排斥量(mutex)

    线程同步-相互排斥量(mutex) 线程同步 多个线程同一时候訪问共享数据时可能会冲突,于是须要实现线程同步. 一个线程冲突的演示样例 #include <stdio.h> #includ ...

  2. 【C/C++多线程编程之六】pthread相互排斥量

    多线程编程之线程同步相互排斥量       Pthread是 POSIX threads 的简称,是POSIX的线程标准.          Pthread线程同步指多个线程协调地,有序地同步使用共享 ...

  3. Linux程序设计学习笔记----多线程编程线程同步机制之相互排斥量(锁)与读写锁

    相互排斥锁通信机制 基本原理 相互排斥锁以排他方式防止共享数据被并发訪问,相互排斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个相互排斥锁逻辑上绑定之后,对该资源的訪问操作例如以下: ...

  4. Linux多线程同步之相互排斥量和条件变量

    1. 什么是相互排斥量 相互排斥量从本质上说是一把锁,在訪问共享资源前对相互排斥量进行加锁,在訪问完毕后释放相互排斥量上的锁. 对相互排斥量进行加锁以后,不论什么其它试图再次对相互排斥量加锁的线程将会 ...

  5. μCOS-II系统之事件(event)的使用规则及Semaphore的相互排斥量使用方法

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wavemcu/article/details/27798493 ****************** ...

  6. WinCE C#程序,控制启动时仅仅能启动一个程序,使用相互排斥量来实现,该实现方法測试通过

    </pre><pre code_snippet_id="430174" snippet_file_name="blog_20140718_5_46349 ...

  7. Linux线程相互排斥量--进程共享属性

    多线程中.在相互排斥量和 读写锁的 属性中.都有一个叫 进程共享属性 . 对于相互排斥量,查询和设置这个属性的方法为: pthread_mutexattr_getpshared pthread_mut ...

  8. 多线程相互排斥--mutex(二)

    不知道大家对多线程或多进程间的同步相互排斥的控制机制了解的怎么样,事实上有非常多种方法能够实现这个目的,可是这些方法事实上由4种最主要的方法实现.这4种最主要的方法详细定义例如以下:在这有讲得不正确的 ...

  9. android NDK编程:使用posix多线程与mutex相互排斥同步

    MainActivity.java 调用原生方法 posixThreads(int threads, int iterations) 启动线程 package com.apress.threads; ...

随机推荐

  1. HDU4763-Theme Section(KMP+二分)

    Theme Section Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) To ...

  2. Milk(杭电1070)

    Milk Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  3. 【Swift】学习笔记(六)——函数

    函数  懂编程语言的来说这个是最主要的了,不论什么语言都有函数这个概念.函数就是完毕特定任务的独立代码块. 函数怎么创建: 1.创建一个无參无返回值的函数(实际上全部的函数都有返回值,这个函数返回vo ...

  4. thinkPHP 空模块和空操作、前置操作和后置操作 具体介绍(十四)

    本章节:介绍 TP 空模块和空操作.前置操作和后置操作 具体介绍 一.空模块和空操作 1.空操作 function _empty($name){ $this->show("$name ...

  5. Linux,Docker,Jenkins No such file or directory

    你们先休息下,我先哭哭! 今天在做交接项目的bug修改的时候,在创建文件的时候报错 No such file or directory 然后跟着路径去linux中查看了该路径,但确实存在,并且权限都是 ...

  6. 2.Matlab数值数组及其运算

    2.1引导 2.2一维数组的创建与寻访 2.3二维数组的创建 2.4二维数组元素的标识 2.5二维数组的子数组寻访和赋值 2.6执行数组运算的常用函数 2.7数组运算和矩阵运算 2.8多项式的表达和创 ...

  7. ASP.net 中 OutputCache 指令各个参数的作用

    使用@ OutputCache指令 使用@ OutputCache指令,能够实现对页面输出缓存的一般性需要.@ OutputCache指令在ASP.NET页或者页中包含的用户控件的头部声明.这种方式非 ...

  8. 基于Socket的Winform例子

    一.直接上效果图 二.Socket握手 三.服务端 Thread threadWatch = null;// 负责监听客户端的线程 Socket socketWatch = null;// 负责监听客 ...

  9. canvas实现刮刮卡效果

    canvas实现刮刮卡效果 实现步骤: 设置页面背景图,即刮刮卡底部图片 绘制canvas 刮刮卡顶部图片drawImage 绑定事件 addEventListener  touchstart.tou ...

  10. 手把手教你写带登录的NodeJS爬虫+数据展示

    其实在早之前,就做过立马理财的销售额统计,只不过是用前端js写的,需要在首页的console调试面板里粘贴一段代码执行,点击这里.主要是通过定时爬取https://www.lmlc.com/s/web ...