C++多线程(3)——互斥量、unique_lock()、单例类设计

互斥量mutex

class A
{
    
    
public:
	// 向消息队列中插入元素
	void inMsgRecvQueue()
	{
    
    
		for (int i = 0; i < 100000; i++)
		{
    
    
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			my_mutex.lock();
			msgRecvQueue.push_back(i);
			my_mutex.unlock();
		}
	}

	// 取出元素的加解锁操作
	bool outMsgLULproc(int& command)
	{
    
    
		std::lock_guard<mutex> guard(my_mutex);
		//my_mutex.lock();
		if (!msgRecvQueue.empty())
		{
    
    
			command = msgRecvQueue.front();
			msgRecvQueue.pop_front();
			//my_mutex.unlock();// 注意不同分支退出前都要unlock
			return true;
		}
		//my_mutex.unlock();
		return false;
	}

	// 从消息队列中取出首元素
	void outMsgRecvQueue()
	{
    
    
		int command = 0;
		for (int i = 0; i < 100000; i++)
		{
    
    
			bool result = outMsgLULproc(command);// 将加解锁与其他处理代码分开
			if (result == true)
			{
    
    
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				// 其他处理代码...
			}
			else
			{
    
    
				cout << "outMsgRecvQueue()执行,目前消息队列为空" << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue; // 消息队列
	mutex my_mutex; // 互斥量
};

int main()
{
    
    
	A a;
	thread inThread(&A::inMsgRecvQueue, std::ref(a));
	thread outThread(&A::outMsgRecvQueue, std::ref(a));
	inThread.join();
	outThread.join();

	return 0;
}
  • lock()与unlock()使用注意事项:有lock必然有unlock(),两者一一对应。注意语句不同分支return之前,要unlock()。
  • lock_guard()一条语句取代了lock()与unlock();lock_guard构造函数里执行了mutex::lock(),在析构函数里执行了mutex::unlock()。

死锁

class A
{
    
    
public:
	void thread1()
	{
    
    
		for (int i = 0; i < 100000; i++)
		{
    
    
			cout << "thread1()执行第" << i << "次" << endl;
			my_mutex1.lock();
			my_mutex2.lock();
			my_mutex2.unlock();
			my_mutex1.unlock();
		}
	}

	void thread2()
	{
    
    
		for (int i = 0; i < 100000; i++)
		{
    
    
			cout << "thread2()执行第" << i << "次" << endl;
			my_mutex2.lock();
			my_mutex1.lock();
			my_mutex1.unlock();
			my_mutex2.unlock();
		}
	}
private:
	mutex my_mutex1;
	mutex my_mutex2;
};

int main()
{
    
    
	A a;
	thread inThread(&A::thread1, std::ref(a));
	thread outThread(&A::thread2, std::ref(a));
	inThread.join();
	outThread.join();

	return 0;
}
  • 死锁产生的前提是至少要有两个互斥量(两把锁),两个互斥量在两个子线程中加锁的顺序正好相反。
  • 当thread1执行时,先对my_mutex1加锁,然后当要给my_mutex2加锁时,被上下文切换了,此时thread2执行,先对my_mutex2加锁,然后要对my_mutex2加锁时由于已被thread1上锁,所以两个线程只能互相等待对方解锁,此时死锁产生。
  • 一般解决方案:
    • 保证两个互斥量上锁的顺序一致
    • 使用std::lock()函数模板

std::lock()函数模板

  • 用于处理多个互斥量,给多个互斥量同时加锁
  • 不存在因为加锁的顺序问题导致死锁
  • 如果互斥量中有一个没有锁住,则std::lock()会立即把已经锁住的解锁,直到所有互斥量都锁住,才继续往下运行。
std::lock(my_mutex1, my_mutex2);
my_mutex1.unlock();
my_mutex2.unlock();
  • 但是此时还需要手动解锁。可以使用std::lock_guard的std::adopt_lock参数

std::adopt_lock参数

std::lock(my_mutex1, my_mutex2);
std::lock_guard<mutex> guard1(my_mutex1, std::adopt_lock);
std::lock_guard<mutex> guard2(my_mutex2, std::adopt_lock);
  • std::lock_guard是个RAII(Resource Acquisition is Initialization)资源获取即初始化类。在构造函数中申请内存,析构函数中释放内存。容器、智能指针都是RAII类
  • std::adopt_lock是个结构体对象,起到标记作用:表示这个互斥量已经加过锁了,不需要在std::lock_guard中再上锁

std::unique_lock()

与lock_gard()的区别

  • lock_guard更加的高效,只能在析构函数中解锁
  • unique_lock更加的灵活,但是内存占用更多,效率低

std::try_to_lock参数

  • 使用try_to_lock的前提是不能先去锁这个互斥量
  • unique_lock会尝试去锁这个互斥量,但如果没有锁定成功,会立即返回,并不会阻塞在那里
unique_lock<mutex> guard1(my_mutex, std::try_to_lock);
if (guard1.owns_lock())
{
    
    
	cout << "thread1()尝试执行第" << i << "次————成功" << endl;
}
else
{
    
    
	cout << "thread1()尝试执行第" << i << "次————失败" << endl;
}

std::defer_lock参数

  • 使用defer_lock的前提是不能先去锁这个互斥量

  • defer_lock并没有给mutex加锁,只是初始化了一个没有加锁的mutex,将mutex和一个unique_lock绑定到一起

unique_lock<mutex> guard(my_mutex, std::defer_lock);
guard.lock();// 注意这里调用unique_lock的lock()成员函数

成员函数lock()、unlock()、try_lock()、release()

unique_lock<mutex> guard(my_mutex, std::defer_lock);
guard.lock();
// 处理共享数据代码
//...
guard.unlock();
// 临时处理非共享数据代码
//...
guard.lock();
// 处理共享数据代码
//...
if(guard.try_lock()==true)
{
    
    
    // 处理共享数据代码
    //...
}
else
{
    
    
    cout<<"暂未拿到锁"<<endl;
}
mutex* pm = guard.release();
pm->unlock();// 注意手动解锁
  • lock()加锁
  • unlock()解锁,临时处理非共享数据代码
  • try_lock()尝试给互斥量加锁,不会导致线程阻塞
  • release()返回它所管理的mutex指针,并释放所有权。也就是,这个unique_lock和mutex不再有关系。注意区别release()和unlock()的区别。

粒度

  • 互斥量锁住的代码多少称为锁的粒度
  • 如果锁住的代码少,粒度叫细,执行效率高;如果锁住的代码多,粒度叫粗,执行效率低;

所有权传递

// 方法一
unique_lock<mutex> guard1(my_mutex);
// unique_lock<mutex> guard2(guard1);// 复制所有权是非法的
unique_lock<mutex> guard2(std::move(guard1));// 移动语义,现在相当于guard2和my_mutex绑定到了一起,guard1指向空

// 方法二
unique_lock<mutex> rtn_unique_lock()
{
    
    
    unique_lock<mutex> tmpguard(my_mutex);
    return tmpguard;// 从函数返回一个局部的unique_lock对象是可以的。返回这种局部对象会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数
}
unique_lock<mutex> guard = rtn_unique_lock();

单例设计模式共享数据

单例类

// 单例类
class MyCAS
{
    
    
private:
	MyCAS() {
    
    };// 私有化构造函数
	static MyCAS* m_instance;// 静态成员变量

public:
	static MyCAS* GetInstance() {
    
    
		if (m_instance == NULL)
		{
    
    
			m_instance = new MyCAS();
		}
		return m_instance;
	}
};
MyCAS* MyCAS::m_instance = NULL;// 类静态变量初始化

int main()
{
    
    
	MyCAS* a = MyCAS::GetInstance();
	MyCAS* b = MyCAS::GetInstance();

	return 0;
}

多线程中创建单例类

  • 建议在创建其他线程之前,在主线程中初始化单例类对象。

  • 当在多线程中创建单例类时,可能会面临这个问题:当线程1在初始化单例类对象执行GetInstance()函数时,执行完if (m_instance == NULL)语句后被切换到线程2执行GetInstance()函数,由于线程1并没有执行m_instance = new MyCAS()语句,导致线程2在执行if (m_instance == NULL)时判断没有初始化m_instance,进而执行了m_instance = new MyCAS()语句,当再切换回线程1时,又执行了一遍m_instance = new MyCAS()语句,最终导致这个单例类对象被创建了两个。总之,当在多线程中创建单例类时,可能会创建多个单例类对象。

std::mutex MyCAS_mutex;// 互斥量

// 单例类
class MyCAS
{
    
    
private:
	MyCAS() {
    
    };// 私有化构造函数
	static MyCAS* m_instance;// 静态成员变量

public:
	static MyCAS* GetInstance()
	{
    
    
		if (m_instance == NULL)// 双重锁定(双重检查)
		{
    
    
			unique_lock<mutex> guard(MyCAS_mutex);// 加锁
			if (m_instance == NULL)
			{
    
    
				m_instance = new MyCAS();
			}
		}
        return m_instance;
	}
};

MyCAS* MyCAS::m_instance = NULL;// 类静态变量初始化

void CreateMyCAS()
{
    
    
	MyCAS* p = MyCAS::GetInstance();
	return;
}

int main()
{
    
    
	//MyCAS* a = MyCAS::GetInstance();
	//MyCAS* b = MyCAS::GetInstance();

	thread my_thread1(CreateMyCAS);
	thread my_thread2(CreateMyCAS);
	my_thread1.join();
	my_thread2.join();
	return 0;
}
  • 双重锁定的作用:仅在初始化时加锁,提高效率。

std::call_once

  • std::call_once能够保证函数只被调用一次,需要与一个标记结合使用
std::mutex MyCAS_mutex;
std::once_flag g_flag;

// 单例类
class MyCAS
{
    
    
private:
	MyCAS() {
    
    };// 私有化构造函数
	static MyCAS* m_instance;// 静态成员变量
	
	static void CreateInstance()// 只被调用一次1次的代码
	{
    
    
		m_instance = new MyCAS();
        cout << "CreateInstance被执行了" << endl;
		return;
	}

public:
	static MyCAS* GetInstance()
	{
    
    
		std::call_once(g_flag, CreateInstance);
		return m_instance;
	}
};

MyCAS* MyCAS::m_instance = NULL;// 类静态变量初始化

void CreateMyCAS()
{
    
    
	MyCAS* p = MyCAS::GetInstance();
	return;
}

int main()
{
    
    
	//MyCAS* a = MyCAS::GetInstance();
	//MyCAS* b = MyCAS::GetInstance();

	thread my_thread1(CreateMyCAS);
	thread my_thread2(CreateMyCAS);
	my_thread1.join();
	my_thread2.join();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_34731182/article/details/113476918