目录
第3章 线程间共享数据
3.1 共享数据带来的问题
共享数据,但当一个或多个线程要修改数据时,就会产生很多的麻烦。
3.1.1 条件竞争
并发中竞争条件的形成,取决于一个以上线程的相对执行程序,每个线程都要抢着完成自己的任务。(这很好理解,就比如去电影院买电影票一样,剩下最后一张,抢票大战。。。)
3.1.2 避免恶行条件竞争
最简单的方法:对数据结构采用某种保护机制,确保只有进行修改的线程才能看啊都不变量被破坏的中间状态。
另一个选择是:对数据结构和不变量的设计进行修改,形成一系列不可分割的变化,并且处于稳定状态。
另外一个方式:使用事务的方式处理数据结构的更新(STM software transactional memory 软件事务内存)
保护共享数据结构的最基本的方式:是使用C++标准库提供的互斥量
3.2 使用互斥量保护共享数据
过程:访问共享数据前–>将数据锁住—>访问结束后—>将数据解锁
互斥量:需要编排代码保护数据的正确性(3.2.2节)
需要避免接口间的竞争条件
缺点:造成死锁、对数据保护的太多(太少)(见 3.2.8节)
3.2.1 C++使用互斥量
实例化std::mutex创建互斥量实例,用lock()对互斥量上锁,unlock进行解锁。不过,在实践时要调用函数就要每个函数出口要调用unlock(),麻烦。
C++标准库为互斥量提供了RAII语法的模板类std::lock_guard,在构造时已提供互斥量,析构时进行解锁
//清单3.1 使用互斥量保护列表
#include<list>
#include<mutex>
#include<algorithm>
std::list<int> some_list;
std::mutex some_mutex;
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(some_mutex);
some_list.push_back(new_value);
}
bool list_contains(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();
}
//add_to_list()和list_contains()函数中使用std::lock_guard<std::mutex>,
//让这两个函数对数据访问互斥,后者看不到前者正修改而定列表
后面有C++17中的一种加强版保护数据机制std::scoped_lock (std::scoped_lock guard (std::mutex))
小李子(未上锁)
#include<iostream>
#include<string>
#include<thread>
using namespace std;
void thread_task() {
for (int i = 0; i < 10; i++)
{
cout << "print thread:" << i << endl;
}
}
int main() {
thread t(thread_task);
for (int i = 0; i > -10; i--) {
cout << "print main:" << i << endl;
}
t.join();
return 0;
}
//结果:
/*
print main:0
print main:-1
print main:-2
print main:-3
print main:-4
print main:-5
print main:-6
print main:-7
print main:-8
print main:-9
print thread:0
print thread:1
print thread:2
print thread:3
print thread:4
print thread:5
print thread:6
print thread:7
print thread:8
print thread:9
*/