5. 使用互斥量保护共享数据

Posted ^江流儿^

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5. 使用互斥量保护共享数据相关的知识,希望对你有一定的参考价值。

使用互斥量保护共享数据

  1. C++中使用互斥量
    • C++通过实例化std::mutex创建互斥量,通过调用成员函数lock()进行加锁,unlock()进行解锁,在实践中不推荐直接调用成员函数,因为调用成员函数就意味着,必须记住在每个函数的出口都需要调用unlock(),同时包括异常的情况,C++中推荐使用lock_guard实例对数据进行保护 ,lock_guardmutex都在<mutex>头文件中进行声明
    #include<list>
    #include<mutex>
    std::list<int>li;
    std::mutex li_mutex;
    void add_to_list(int num)
    	std::lock_guard<mutex>guard(li_mutex);
    	li.push_back(num);
    
    bool list_contain(int value_to_find)
    	std::lock_guard<mutex>guard(li_mutex);
    	return std::find(li.begin(),li.end(),value_to_find) != li.end();
    
    
    上述代码中使用全局变量进行保护,通常情况下没有问题,但是大多情况下,互斥量和保护的数据放在同一个类中。
    当一个成员函数返回的是保护数据的指针的或者引用的时候,会破坏数据的保护,具有访问能力的指针或者引用可以访问或者修改被保护的数据,而不会被互斥锁限制,因此在设计互斥量的时候需要能够锁住任何数据的访问方式,不留后门

  1. 精心组织代码来保护共享数据
    • 在确保成员函数不会传出指针或者引用的同时,检查成员函数是否通过指针或者引用的方式了来调用同样重要,函数可能在没有互斥量保护的地方存储这指针或者引用,例如如下代码:
    class foo
     private:
     	int data;
    	mutex _mutex;
     public:
     	template<class Function>
    	void dowork(Function func)
    		lock_guard<mutex>g(_mutex);
    		func(data);
    	
    
    int * danger_pointer;
    void danger_func(int & tmp)
    	danger_pointer = &tmp;
    
    int main()
    	foo f;
    	f.dowork(danger_func);
    
    
    上述代码中,虽然有互斥锁的保护,但是foo中的数据依旧通过指针被传递给了danger_pointer,切记不要将受保护的数据的指针或者 引用传递到互斥锁的作用和与之外

  1. 发现接口内在的条件竞争
    • 元素操作
      在多线程开发中,对于共享资源存在竞争条件,例如上一个线程可能对栈进行了某种判别条件,但是时间片很快切给了下一个线程,可能会导致上一个进程的判别条件发生改变,但是程序不会重复执行判别代码,如下;
    stack<int>sta;
    void process_1()
    	if(sta.empty())
    		...
    	
    
    void process_2()
    	sta.push(...);
    
    int main()
    	thread t1(process_1);
    	thread t2(process_2);
    	t1.join();
    	t2.join();
    
    
    在上述例子中,可能第一个进程刚 判断了栈为空,还没进行下面的操作,第二个线程就向栈中添加了元素,第一个线程的判断条件将不在成立,但是仍然会进行下面的操作。

  1. 多线程中的元素转移问题
    举个例子,假设有一个stack<vector>,当vector中有大量元素的时候,拷贝其中的内容可能会发生bad_alloc的异常,如果此时调用stack中的pop方法,可能造成栈顶元素确实从栈中弹出,但是并没有足够的空间对其进行拷贝,这个时候,有就会丢失,基本的解决办法就是先拷贝,再弹出,这样又会产生空间不足的问题。针对这种问题,有如下几个方法可以进行解决。
    • 传入一个引用
      将变量的引用作为参数传递到pop函数当中
    std::vector<int>res;
    sta.pop(res);
    此处pop函数具体如下:
    void pop(vector<int>& res)
    	lock_guard<mutex>loc(m);
    	if(sta.empty())
    		throw empty_stack();
    	res = data.top();
    	data.pop();
    
    
    • 返回弹出值的指针
      返回指针和返回引用的原理是一样的
    auto ptr = sta.pop();
    此处pop函数的具体实现如下:
    std::shared_ptr<T> pop()
    	std::loc_guard<mutex>loc(m);
    	if(data.empty()) throw empty_stack();
    	shared_ptr<T>const res(std::make_shared<T>(data.top()));
    	return res;
    
    

  1. 死锁问题
    死锁产生的四个必要条件分别为1. 互斥条件 2. 不可剥夺条件 3. 请求和保持条件 4. 循环等待条件

互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,
而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
循环等待条件:在发生死锁时必然存在一个进程等待队列P1,P2,…,Pn,其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。
————————————————
版权声明:本文为CSDN博主「Hyacinth_Dy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jyy305/article/details/70077042

死锁的避免方法如下:

  • 避免嵌套所
  • 避免在持有锁的时候调用用户提供的代码
  • 按照一定的顺序上锁
  • 使用锁的层次结构
    对于这个方法,他的意思是对每个锁进行级别标注,当低层次的锁被锁上的时候,高层次的锁不允许上锁,原理类似于按一定的顺序上锁

以上是关于5. 使用互斥量保护共享数据的主要内容,如果未能解决你的问题,请参考以下文章

(转载)pThreads线程 线程同步--互斥量/锁

RT-Thread快速入门-互斥量

Linux 多线程同步机制:互斥量信号量条件变量

Unix网络编程-同步

C11线程管理:互斥锁

理解互斥量和信号量