C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁

一、多线程的状态

  • 初始化 (Init):该线程正在被创建。
  • 就绪 (Ready):该线程在就绪列表中,等待CPU的调度。
  • 运行 (Running):该线程正在运行。
  • 阻塞(Blocked):该线程被阻塞挂起。Blocked状态包括:pend(锁、事件、信号量等阻塞)、suspend(主动pend)、delay(延时阻塞)、pendtime(因为锁、事件、信号量时间等超时等待)。
  • 退出(Exit):该线程运行结束,等待父线程后收其控制块资源。

C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第1张图片

二、多线程之间的竞争关系和临界区

1、竞争关系(Race condition)

多个线程同时读写共享资源
例如:

#include
#include
#include
using namespace std;
void Test()
{
	cout << "---------------" << endl;
	cout << "Test 1 " << endl;
	cout << "Test 2 " << endl;
	cout << "-----------------" << endl;
}

int main()
{
	for (int i = 0; i < 10; i++)
	{
		thread th(Test);
		th.detach();
	}
	getchar();
	return 0;
}

C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第2张图片

我们创建了10个线程,但是每个线程在运行时都是无规律的,对于相同的输出都是处于竞争状态。

2、临界区(Critical Section)

我们使用C++ 11 自带的mutex(互斥锁)来解决竞争状态。
C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第3张图片
原理:
当n个线程要访问我们临界区时,只允许一个线程进入,其余线程会阻塞在临界区外,每当一个线程运行完临界区代码后,会允许阻塞在临界区外的一个线程再次访问临界区。

3、lock()和try_lock()区别

  • lock():**lock 函数用于获取互斥锁。**如果当前互斥锁已被其他线程占用,则 lock 函数将阻塞当前线程,直到互斥锁可用为止。一旦成功获取到互斥锁,该线程将独占互斥锁,并可以继续执行后续的操作。在完成互斥锁所需的操作后,线程应该调用 unlock 函数来释放互斥锁,以便其他线程可以获取它。阻塞式的,会一直等待直到成功获取到互斥锁。

  • try_lock()try_lock 函数用于尝试获取互斥锁,但是它并不会阻塞当前线程。如果当前互斥锁可用,try_lock 函数将立即获取到互斥锁,并返回 true。如果当前互斥锁被其他线程占用,try_lock 函数将立即返回 false,而不会阻塞线程。通过检查返回值,线程可以根据情况选择等待或放弃获取互斥锁。非阻塞式的,立即返回获取互斥锁的结果,线程可以根据返回值做出相应的处理。
    例如:这里有10个线程,只有两个线程进入,而其它8个线程并不会阻塞住。
    C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第4张图片

  • 使用 lock 函数时要注意避免死锁的情况,而 try_lock 函数则可以更灵活地处理互斥锁的获取失败情况。

4、互斥锁的坑(线程抢占)

C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第5张图片
可以看到在这段代码中,本来1号线程运行完,执行unlock()后,应该其他阻塞的线程应该进入代码内,但是发现进入的依旧是1号ID的线程。
原因:当我们执行完unlock()后,因为代码执行速度就几微秒的时间,但是我们CPU调度探测一次得过一段时间,所以导致了这个问题。(也就是说我们执行的速度快于检测的速度,导致我们线程1号执行完后又再次进入循环,又把资源锁上了)
解决:只需要在unlock()后面等待一段时间就可以,让我们CPU有充裕的时间来调度检测。C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第6张图片

三、互斥锁(mutex)

上面样例中设计到这里就不在过多描述

四、超时锁(timed_mutex)

C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第7张图片
C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第8张图片

超时锁可以让阻塞的线程不断尝试进入,如果超过等待时间,就会执行日志结果,可以帮助我们更好的检查线程状态,检查是否超时或者是否存在死锁。

五、可重入锁(recursive_mutex)

1.可重入锁可以解决哪类情况?

如果直接用互斥锁C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第9张图片
显然会直接报错,因为我们要进入下一个锁内需要先解除当前锁C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第10张图片

显然是让人头疼麻烦的,我们必须每次得考虑解锁的时机。为了解决此类情况,我们有了可重入锁。

2.可重入锁。

C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第11张图片
功能如可以多重进入锁区域,要注意的是,进入几个锁就要在最后解除几个锁,这样才能让其他阻塞线程进入。

六、共享锁(shared_mutex)

C++ 14中引入了shared_timed_mutex;
C++ 17中引入了shared_mutex;

1、 共享锁的功能

假设有6个线程同时要访问同一片区域数据,有5个线程要读数据,而有一个线程要写数据,那么共享锁可以让读线程的5个线程同时进行读,然后阻塞写数据的线程。当写线程进入时,阻塞5个读的线程,写线程开始写数据。这就是共享锁的基本作用。

2、代码

#include
#include
#include
using namespace std;
shared_mutex smux;
int num;
void thread_read(int i)
{
	for (;;)
	{
	smux.lock_shared();
	cout << "num is :" <<num<< endl;
	this_thread::sleep_for(1000ms);
	smux.unlock_shared();
	this_thread::sleep_for(10ms);
	}
}
void thread_write(int i)
{
	for (;;)
	{
	smux.lock_shared();
	num++;
	cout << "add num" << endl;
	this_thread::sleep_for(1000ms);
	smux.unlock_shared();
	this_thread::sleep_for(10ms);
	}

}

int main()
{
	for (int i = 0; i < 5; i++)
	{
		thread th(thread_read,i + 1);
		this_thread::sleep_for(50ms);
		th.detach();
	}
	for (int i = 0; i < 2; i++)
	{
		thread th(thread_write, i + 1);
		this_thread::sleep_for(50ms);
		th.detach();
	}
	getchar();
	return 0;
}

C++多线程学习[四]:多线程的通信和同步、互斥锁、超时锁、共享锁_第12张图片

你可能感兴趣的:(多线程学习,c++,学习,开发语言)