C++并发编程2——为保护数据加锁(一)
来源:互联网 发布:c语言加法编程自定义 编辑:程序博客网 时间:2024/05/16 12:58
找到问题的解决办法,而不是找蹩脚的接口。
在应届生面试的时候,很多面试官都会问——“多线程如何共享资源”。在操作系统层面上可以给出若干关键词答案,但是在语言层面,这个问题考虑的就没有那么简单了。同时,很多人会将多线程数据共享和线程同步混淆。有关线程同步,我们会在接下来的章节里着重阐述。本文主要聚焦于保护共享数据,首先从加锁入手,进而扩展到加锁无法解决的问题,最后会给出一些其他保护方案。
参数入栈
一个存放参数的栈数据结构,相同函数的参数必须要在栈中相连,我们来实现这个功能,看下面代码:
#include <stack>#include <iostream>class MutexTest{public: MutexTest(): m_charStack() { } ~MutexTest() { } void Push(int n, char c) { for (int i = 0; i < n; ++i) { m_charStack.push(c); std::cout << c; } std::cout << std::endl; }private: std::stack<char> m_charStack;};MutexTest test;std::thread mutexTestThread1(&MutexTest::Push, &test, 10, 'a');std::thread mutexTestThread2(&MutexTest::Push, &test, 10, 'b');mutexTestThread1.join();mutexTestThread2.join();
上面这段代码的执行结果是不确定的,这是因为我们无法预测线程的执行顺序,多个线程共享同一个数据栈存在竞态条件(Race Condition)。所以我们可能得到下面的执行结果,所有的参数都是交叉在一起的,这不是我们想要的结果。
aabbbbbbbaaaaaaaabbb
竞态条件
是多线程编程的噩梦,为什么会出现竞态条件可以自行百度,我们主要是为了解决这个问题。让最终执行的结果为:
aaaaaaaaaabbbbbbbbbb
参数入栈保护
std::mutex
是C++11提供的数据加锁类,C++中通过实例化 std::mutex 创建互斥量,通过调用成员函数lock()进行上锁,unlock()进行解锁。
class MutexTest{public: MutexTest(): m_mutex(), m_charStack() { } ~MutexTest() { } void Push(int n, char c) { m_mutex().lock(); for (int i = 0; i < n; ++i) { m_charStack.push(c); std::cout << c; } std::cout << std::endl; m_mutex().unlock(); }private: std::mutex m_mutex; std::stack<char> m_charStack;};
这段代码和上面的不同点就是使用std::mutex,在访问m_charStack之前上锁,其他线程就必须要等待解锁后才能访问m_charStack。如果我们忘记解锁,那么m_charStack就再也无法被访问了,所以有必要用RAII类std::lock_guard
进行封装——构造时上锁,析构时解锁。
void MutexTest::Push(int n, char c){ std::lock_guard<std::mutex> lg(m_mutex); for (int i = 0; i < n; ++i) { m_charStack.push(c); std::cout << c; } std::cout << std::endl;}
C++还提供了std::unique_lock
锁,相对于std::lock_guard
,该锁提供了更好地上锁和解锁灵活性控制。std::unique_lock
以独占所有权的方式来管理mutex对象的上锁和解锁操作。我们来看看其用法
// unique_lock constructor example#include <iostream> #include <thread> #include <mutex> std::mutex foo,bar;void task_a () { std::lock (foo,bar); // simultaneous lock (prevents deadlock) std::unique_lock<std::mutex> lck1 (foo,std::adopt_lock); std::unique_lock<std::mutex> lck2 (bar,std::adopt_lock); std::cout << "task a\n"; // (unlocked automatically on destruction of lck1 and lck2)}void task_b () { // foo.lock(); bar.lock(); // replaced by: std::unique_lock<std::mutex> lck1, lck2; lck1 = std::unique_lock<std::mutex>(bar,std::defer_lock); lck2 = std::unique_lock<std::mutex>(foo,std::defer_lock); std::lock (lck1,lck2); // simultaneous lock (prevents deadlock) std::cout << "task b\n"; // (unlocked automatically on destruction of lck1 and lck2)}int main (){ std::thread th1 (task_a); std::thread th2 (task_b); th1.join(); th2.join(); return 0;}
现在我们终于得到了我们想要的结果,可惜在很多时候加锁并不是解决数据共享的万能药。下一节,我们将会涉及到一些加锁无法解决的数据共享问题。
- C++并发编程2——为保护数据加锁(一)
- C++并发编程2——为共享数据加锁(二)
- C++并发编程2——为共享数据加锁(三)
- C++并发编程2——为共享数据加锁(四)
- 事务并发——并发控制(加锁)
- C++11 并发编程教程 - Part 2 : 保护共享数据
- Java并发编程实践笔记之——加锁机制(Locking)
- java并发编程(三)客户端加锁与组合
- java并发编程--AbstractQueuedSynchronizer加锁和解锁分析(二)
- C++11 并发教程——Part2:保护共享数据
- Java并发编程(五)保护块
- C语言实现数据保护算法(2)——AES加解密
- 《Java并发编程实战》---线程安全性---加锁
- MySQL并发更新数据加锁处理
- 80386保护模式——保护模式简介(一)
- iOS并发编程(一)——thread
- boost并发编程(一)——互斥锁
- Java并发编程(一)——线程
- 求值
- JAVA开发环境安装及配置
- 第五周:Raptor:选择结构。
- Spring之AOP(二)---前置通知
- 5-计算圆的周长
- C++并发编程2——为保护数据加锁(一)
- 多线程的而应用场景(同时干多件事情)
- hive sql优化-join Mapjoin Group by
- Java算法5--最长公共子序列
- SVD数据降维
- 第五周项目2--输入矩形的长和宽计算面积
- sbt配置
- HDU 2055 An easy problem
- 第五周—长方形面积