C++线程中几类锁的详解

时间:2021-07-20 05:31:58

 

C++线程中的几类锁

多线程中的锁主要有五类:互斥锁条件锁自旋锁读写锁递归锁。一般而言,所得功能与性能成反比。而且我们一般不使用递归锁(C++提供std::recursive_mutex),这里不做介绍。

 

互斥锁

==互斥锁用于控制多个线程对它们之间共享资源互斥访问的一个信号量。==也就是说为了避免多个线程在某一时刻同时操作一个共享资源,例如一个全局变量,任何一个线程都要使用初始锁互斥地访问,以避免多个线程同时访问发生错乱。

在某一时刻只有一个线程可以获得互斥锁,在释放互斥锁之前其它线程都不能获得互斥锁,以阻塞的状态在一个等待队列中等待。

头文件:#include

类型:std::std::mutex、std::lock_guard

用法:在C++中,通过构造std::mutex的实例创建互斥单元,调用成员函数lock()来锁定共享资源,调用unlock()来解锁。不过一般不使用这种解决方案,更多的是使用C++标准库中的std::lock_guard类模板,实现了一个互斥量包装程序,提供了一种方便的RAII风格的机制在作用域块中。

关于RAII惯用法的介绍:。。。

示例代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <thread>//C++11线程库是跨平台的
#include <mutex>//C++互斥锁
#include <vector>
#include <windows.h>
int g_num = 0;
std::mutex g_mutex;
void ThreadFunc(int a)
{
    cout << "启动线程:" << a << endl;
    for (int i = 0; i < 1000000; i++)
    {
        //g_mutex.lock();
        std::lock_guard<std::mutex> m(g_mutex);//互斥量包装程序
        g_num++;
        //g_mutex.unlock();
    }
}
 
int main()
{
    for (int i = 0; i < 4; i++)
    {
        std::thread t(ThreadFunc, i);
        t.detach();
    }
    Sleep(2000);
    cout << "g_num:" << g_num << endl;
    return 0;
}
 
//高阶版,将上述main()函数的函数名更改,再更改以下的mainTest()即可执行。两个方法的执行的结果相同,原理也相同。
int mainTest()
{
    std::vector<std::thread *> ts;
    for (int i = 0; i < 4; i++)
    {
        std::thread *t = new std::thread(ThreadFunc, i);
        //t.detach();
        ts.push_back(t);
    }
    for (auto begin = ts.begin(); begin != ts.end(); begin++)
        (*begin)->join();
    Sleep(2000);
    cout << "g_num:" << g_num << endl;
    return 0;
}

C++线程中几类锁的详解

TIPS:注意std::cout和std::end都是线程不安全的,所以才会出现线程1和线程3在一行,原因就是线程1未执行cout<<endl。CPU的时间片就已经用完了,CPU转移执行线程3后,再执行线程1的cout<<endl。

具体C++11中thread库join和detach的区别可参考:http://www.zzvips.com/article/227409.html

 

条件锁

条件锁就是所谓的条件变量,当某一个线程因为某个条件未满足时可以使用条件变量使该程序处于阻塞状态,一旦条件满足则以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。最为常见的就是再线程池中,初始情况下因为没有任务使得任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒该线程来处理这个任务。

 

自旋锁

互斥锁和条件锁都是比较常见的锁,比较容易理解。接下来用互斥锁和自旋锁的原理相互比较,来理解自旋锁。

假设我们有一台计算机,该计算机拥有两个处理器core1和core2.现在在这台计算机上运行两个线程:T1和T2,且T1和T2分别在处理器core1和core2上面运行,两个线程之间共享一份公共资源Public。

首先我们说明互斥锁的工作原理,互斥锁是一种sleep-waiting的锁。假设线程T1访问公共资源Public并获得互斥锁,同时在core1处理器上运行,此时线程T2也想要访问这份公共资源Public(即想要获得互斥锁),但是由于T1正在使用Public使得T2被阻塞。当T2处于阻塞状态时,T2被放入等待队列中,处理器core2会去处理其它的任务而不必一直等待(忙等)。也就是说处理器不会因为线程被阻塞而空闲,它会去处理其它事务。

然后我们说明自旋锁的工作原理,自旋锁是一种busy-waiting的锁。也就是说,如果T1正在使用Public,而T2也想使用Public,此时T2肯定是得不到这个自旋锁的。与互斥锁相反,此时运行T2的处理器core2会一直不断地循环检查Public使用可用(自旋锁请求),直到获得到这个自旋锁为止。

从“自旋锁”的名称也可以看出,如果一个线程想要获得一个被使用的自旋锁,那么它会一直占用CPU请求这个自旋锁使得CPU不能去做其它的事情,知道获取这个锁为止,这就是“自旋”的含义。当发生阻塞时,互斥锁可以让CPU去处理其它的事务,但自旋锁让CPU一直不断循环请求获取这个锁。通过比较,我们可以明显的得出结论:“自旋锁”是比较消耗CPU的。

 

读写锁

读写锁我们可以借助于“读者-写者”问题进行理解。接下来我们简单说下“读者-写者”问题。

计算机中某些数据被多个进程共享,对数据库的操作有两种:一种是读操作,就是从数据库中读取数据不会修改数据库中内容;另一种就是写操作,写操作会修改数据库中存放的数据。因此可以得到我们允许在数据库上同时执行多个“读”操作,但是某一时刻只能在数据库上有一个“写”操作来更新数据。这就是简单的读者-写者模型。

 

参考博客

http://www.zzvips.com/article/204411.html

 

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容!

原文链接:https://blog.csdn.net/qq135595696/article/details/121411703