C++ 并发编程 | 锁

发布时间:2024年01月17日


前言

线程之间的锁有:互斥锁、条件锁、递归锁、读写锁、自旋锁。

busy-waiting锁: 自旋锁是busy-waiting锁。如果线程去申请自旋锁时,发现此时锁被占用了。此时、线程会一直不断地循环检查自旋锁是否可用,直到获取到这个自旋锁为止。

sleep-waiting锁: 除自旋锁外,其他的都是sleep-waiting锁。如果线程去申请锁时,发现此时锁被占用了,线程会处于阻塞状态,被放入到等待队列中去,处理器会去处理其他任务而不必一直等待。

一、锁

1、互斥锁

1.1、定义

mutex又称互斥量,提供了独占所有权的特性,即不支持递归地对 std::mutex 对象上锁。mutex常见的成员函数如下:

成员函数功能
构造函数std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的
lock()调用线程将锁住该互斥量
unlock()解锁,释放对互斥量的所有权
try_lock()尝试锁住互斥量,如果互斥量被其他线程占有,则当前调用线程返回 false,而并不会被阻塞掉

1.2、使用

下面是一个使用mutex的示例

#include <mutex>
#include <iostream>
#include <thread>

#define THREAD_MAX_COUNT 10

std::mutex mtx;
int g_count = 0;

void ThreadEntry()
{
    for (int i = 0; i < 100; i ++)
    {
        mtx.lock();
        ++g_count;
        mtx.unlock();
    }
}

int main()
{
    std::thread threads[THREAD_MAX_COUNT];
    for (int i = 0; i < THREAD_MAX_COUNT; i++)
    {
        threads[i] = std::thread(ThreadEntry);
    }

    for (auto& th : threads)
    {
        th.join();
    }

    std::cout << " successful increases of the counter " << g_count << std::endl;
    return 0;
}

1.3、死锁

已经获取到互斥锁的线程再次尝试获取互斥锁就会产生死锁,例如:

std::mutex mtx;
int g_count = 0;

void func2()
{
    mtx.lock();
    g_count++;
    mtx.unlock();
}

void func1()
{
    mtx.lock();
    func2();  // func2函数里面有获取互斥锁的操作,会产生死锁
    mtx.unlock();
}

int main()
{
    std::thread td = std::thread(func1);
    td.join();

    return 0;
}

1.4、死锁检测机制

检测任务数据结构、定时器、无锁队列,等组件实现一个死锁检测机制

struct Node
{
    std::thread td;         // 线程ID
    unsigned int interval;  // 检测周期
    int strategy;           // 检测到死锁时使用的策略
};

2、递归锁

递归锁允许同一个线程多次获取该互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。

#include <mutex>
#include <iostream>
#include <thread>

std::recursive_mutex mutex;
int g_count = 0;

void func2()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    g_count++;
}

void func1()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    func2();
}

int main()
{
    std::thread td = std::thread(func1);
    td.join();

    std::cout << "count = " << g_count << std::endl;
    return 0;
}

3、自旋锁

3.1、定义

自旋锁(spin lock)属于busy-waiting类型锁。自旋锁最多只能被一个可执行线程持有,如果一个可执行线程试图获得一个被其它线程持有的自旋锁,那么线程就会一直进行忙等待,自旋(空转),等待自旋锁重新可用。如果自旋锁未被争用,请求锁的执行线程便立刻得到自旋锁,继续执行。

3.2、互斥锁与自旋锁的区别

下面是互斥锁与自旋锁之间的区别,如下:

互斥锁自旋锁
锁被占用线程变成阻塞状态被放入到等待队列中去,处理器会去处理其他任务而不会一直等待,也就是说处理器不会因为线程阻塞而空闲着线程的状态不会发生变换,会一直不断地循环检查自旋锁是否可用,直到获取到这个自旋锁为止。
应用场景执行的任务比较长执行的任务比较短

4、unique_lock和lock_guard

C++ 提供了unique_lock与lock_guard模版,通过RAII的方式对锁进行管理

4.1、unique_lock

std::unique_lock 提供了更多的灵活性。它允许在构造时选择是否锁定互斥量,以及在后续的使用中手动锁定和解锁。这使得它更适用于一些需要条件变量、超时等更复杂场景的情况。

std::unique_lock 可以与 std::condition_variable 一起使用,以实现等待某个条件成立的操作。它支持 std::unique_lock::wait、std::unique_lock::notify_one 和 std::unique_lock::notify_all 等操作。

4.2、lock_guard

std::lock_guard 提供了一种简单、直观的方式来管理互斥锁。在构造时锁定互斥量,在析构时自动解锁。因为它的设计目标是简洁性,所以没有提供手动解锁的功能。如果你的代码只需进行简单的锁定和解锁操作,std::lock_guard 是一个不错的选择。示例:

#include <mutex>
#include <iostream>
#include <thread>

std::recursive_mutex mutex;
int g_count = 0;

void func1()
{
    std::lock_guard<std::recursive_mutex> lock(mutex);
    g_count++;
}

int main()
{
    std::thread td = std::thread(func1);
    td.join();

    std::cout << "count = " << g_count << std::endl;
    return 0;
}

文章来源:https://blog.csdn.net/cloud323/article/details/135650017
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。