C++多线程3-共享数据操作保护

目录:
1.多线程操作共享数据引出的问题
2.解决多线程操作共享数据的方法:锁
3.互斥量mutex的概念和用法
4.lock普通锁的用法
5.lock_guard类模板的用法
6.死锁的概念和解决
7.unique_lock的用法

1.多线程操作共享数据引出的问题
代码举例:

//我们用Test::set函数中作为多线程的回调函数,在这个回调函数中打印vector的size,发现它会出现段错误。
#include <iostream>
#include <thread>
#include <memory>
#include <vector>
#include <string>

using namespace std;

class Test
{
public:
	void set(const int index)
	{
		string str = "hello" + to_string(index);
		str_mv.push_back(str);
		cout << "线程id=" << std::this_thread::get_id() << ",size=" << str_mv.size() << endl;
	}
	
private:
	vector<string> str_mv;
};

int main()
{
	Test test;
	thread th[10000];
	for (int i=0; i<10000; i++)
	{
		th[i] = thread(&Test::set, &test, i);
	}
	
	for (int i=0; i<10000; i++)
	{
		if (th[i].joinable())
			th[i].join();
	}
	return 0;
}

//打印结果
root@epc:/home/share/test#  ./test
...
线程id=139743976642304,size=4086
线程id=139743968249600,size=4087
线程id=139743959856896,size=4088
线程id=139743951464192,size=4089
Segmentation fault
root@epc:/home/share/test# 

2.解决多线程操作共享数据的方法:锁
在多线程操作共享数据时,会存在多个线程同时访问共享数据的情况,如果它们的操作只是读,那么没有问题,但是如果同时进行写和读就会出现问题,很容易造成内存访问错误。因此我们通过锁的机制,使得线程排队访问共享数据。

3.互斥量mutex的概念和用法
互斥量的作用是管理共享资源或一小段代码。它有两种状态解锁和加锁。当多个线程访问共享资源时,将随机选择一个线程并分配锁去处理共享资源,其余线程则会被阻塞在该互斥量上,待该线程完事后,互斥量将锁交给另外一个线程,依次循环,直至所有线程处理完毕。
互斥量的头文件是mutex,它对于所有线程都是唯一的因此必须声明它为全局变量或者类静态成员。

//互斥量的普通锁举例
#include <iostream>
#include <thread>
#include <memory>
#include <vector>
#include <string>
#include <mutex>

using namespace std;

class Test
{
public:
	void set(const int index)
	{
		mutex_m.lock();
		string str = "hello" + to_string(index);
		str_mv.push_back(str);
		cout << "线程id=" << std::this_thread::get_id() << ",size=" << str_mv.size() << endl;
		mutex_m.unlock();
	}
	
private:
	vector<string> str_mv;
	static mutex mutex_m;
};

mutex Test::mutex_m;

int main()
{
	Test test;
	thread th[10000];
	for (int i=0; i<10000; i++)
	{
		th[i] = thread(&Test::set, &test, i);
	}
	
	for (int i=0; i<10000; i++)
	{
		if (th[i].joinable())
			th[i].join();
	}
	return 0;
}
//打印结果
root@epc:/home/share/test#  ./test
...
线程id=140531608835840,size=9998
线程id=140531617228544,size=9999
线程id=140531625621248,size=10000
root@epc:/home/share/test# 

4.lock普通锁的用法
互斥量使用了lock()对资源上锁后,一定要调用unlock()进行解锁,而且顺序不可颠倒,否则执行程序会报异常。lock()和unlock()的位置限定了加锁资源的范围。但很多时候,程序员很容易会忘记unlock()的使用,因此C++引入了类似于智能指针机制的模板类,比如lock_guard,当它的对象生存期结束时,析构函数会调用unlock()函数。
异常结果如下:
root@epc:/home/share/test# ./test
线程id=139657018976000,size=1
terminate called after throwing an instance of ‘std::system_error’
what(): Resource temporarily unavailable
Aborted
root@epc:/home/share/test#

5.lock_guard类模板的用法
lock_guard使用比较简单高效,构造函数时拿到锁,析构函数时释放锁。

//lock_guard类模板的用法
class Test
{
public:
	void set(const int index)
	{
		lock_guard<mutex> lock(mutex_m);
		string str = "hello" + to_string(index);
		str_mv.push_back(str);
		cout << "线程id=" << std::this_thread::get_id() << ",size=" << str_mv.size() << endl;
	}
	
private:
	vector<string> str_mv;
	static mutex mutex_m;
};

mutex Test::mutex_m;

6.死锁的概念和解决
死锁最直观的现象是程序没有反应,此时陷入所有线程被阻塞的状态。根据如下例子分析,死锁的根本原因就是当存在多个锁时它们锁的顺序不一致,解决的办法就是锁的顺序保持一致。
解决办法:

//死锁的解决方法:锁的顺序保持一致
class Test
{
public:
	void set()
	{
		//线程执行这段代码需要先获取mutex1_m锁和mutex2_m锁;
		lock_guard<mutex> lock1(mutex1_m);	
		lock_guard<mutex> lock2(mutex2_m);
		string str = "hello";
		str_mv.push_back(str);
		cout << "线程id=" << std::this_thread::get_id() << "进行set" << endl;
	}
	
	void get()
	{
		//线程执行这段代码需要先获取mutex1_m锁和mutex2_m锁;
		lock_guard<mutex> lock1(mutex1_m);
		lock_guard<mutex> lock2(mutex2_m);
		cout << "线程id=" << std::this_thread::get_id() << "进行get" << endl;
	}
	
private:
	vector<string> str_mv;
	static mutex mutex1_m;
	static mutex mutex2_m;
};

死锁代码用如下代码举例:

//死锁代码用如下代码举例:
//线程A执行set函数需先获取mutex1_m锁然后获取mutex2_m锁,线程B执行get函数需先获取mutex2_m锁然后获取mutex1_m锁;
//存在这种情况,当线程A获取到mutex1_m锁时,发现无法获取mutex2_m锁,原因就是线程B正在使用mutex2_m锁,
//于是等待线程B释放mutex2_m锁,同时线程B也在等待线程A释放mutex1_m锁;这就造成了死锁的状态,大家都处于阻塞状态。
#include <iostream>
#include <thread>
#include <memory>
#include <vector>
#include <string>
#include <mutex>

using namespace std;

class Test
{
public:
	void set()
	{
		//线程执行这段代码需要先获取mutex1_m锁和mutex2_m锁;
		lock_guard<mutex> lock1(mutex1_m);	
		lock_guard<mutex> lock2(mutex2_m);
		string str = "hello";
		str_mv.push_back(str);
		cout << "线程id=" << std::this_thread::get_id() << "进行set" << endl;
	}
	
	void get()
	{
		//线程执行这段代码需要先获取mutex2_m锁和mutex1_m锁;
		lock_guard<mutex> lock2(mutex2_m);
		lock_guard<mutex> lock1(mutex1_m);
		cout << "线程id=" << std::this_thread::get_id() << "进行get" << endl;
	}
	
private:
	vector<string> str_mv;
	static mutex mutex1_m;
	static mutex mutex2_m;
};

mutex Test::mutex1_m;
mutex Test::mutex2_m;

int main()
{
	Test test;
	thread th1[5000];
	thread th2[5000];
	for (int i=0; i<5000; i++)
	{
		th1[i] = thread(&Test::set, &test);
		th2[i] = thread(&Test::get, &test);
	}
	
	for (int i=0; i<5000; i++)
	{
		if (th1[i].joinable())
			th1[i].join();
		if (th2[i].joinable())
			th2[i].join();
	}
	return 0;
}

7.unique_lock的用法
内容比较多,会在后续补充。

发布了32 篇原创文章 · 获赞 3 · 访问量 1411

猜你喜欢

转载自blog.csdn.net/m0_37582216/article/details/101319950
今日推荐