整理lock的相关用法

[笔记整理]
多线程的情况下,如果使用mutex来完成锁的相关操作,则需要手动进行锁的加锁和解锁。如果出现忘记解锁的情况,可能会导致程序出现死锁等问题。
这种情况下使用标准库提共的lock相关的模板类比较合适。
标准库提供的lock主要有下面几个:

  • lock_guard(c++11)
  • unique_lock(c++11)
  • shared_lock(c++14)

lock_guard

参考链接:

lock_guard在构造时会进行lock,析构时会调用unlock。不需要通过手动去做释放。
如果构造时,互斥资源被其他线程占有,则会阻塞直到获取锁。与mutex.lock()的效果相同。

示例:

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

std::mutex mtx;
int32_t gNum = 0;

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    std::lock_guard<std::mutex> lock(mtx);
    int32_t i=0;
    std::this_thread::sleep_for(std::chrono::seconds(3));   //模拟一个耗时操作
    while (i < 5000)
    {
        gNum++;
        i++;
    }

    std::cout << id << "  quit\n";
}


int main()
{

    std::thread t1(increase, 1);
    std::thread t2(increase, 2);

    t1.join();
    t2.join();

    std::cout << gNum << std::endl;

    return 0;
}

运行结果:

1  increase in
2  increase in
1  quit
2  quit
10000

unique_lock

参考链接:

构造函数

unique_lock存在下面几种构造函数。
构造函数

基本用法:

定义一个lock对象,在构造函数中会调用mutex.lock()来获取锁,如果获取锁失败的话,也会阻塞当前线程,直到获取锁成功。
在lock对象被销毁时,会在析构函数中调用mutex.unlock()解锁。
也可以在对象被销毁之前,可以手动解锁再获取锁。

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

std::mutex mtx;
int32_t gNum = 0;

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        std::unique_lock<std::mutex> lock(mtx);

        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}


int main()
{

    std::thread t1(increase, 1);
    std::thread t2(increase, 2);

    t1.join();
    t2.join();

    std::cout << gNum << std::endl;

    return 0;
}

运行结果

1  increase in
2  increase in
1  quit
2  quit
10000

使用标签

  • std::try_to_lock:在定义lock对象时,会调用mutex.try_lock(),然后通过owns_lock()函数来判断时候成功获取到锁。
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int32_t gNum = 0;

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);

        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
            return;
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}


int main()
{

    std::thread t1(increase, 1);
    std::thread t2(increase, 2);

    t1.join();
    t2.join();

    std::cout << gNum << std::endl;

    return 0;
}

运行结果,thread 2没有获取到锁之后返回了。

1  increase in
1  get lock ok
2  increase in
2  get lock fail
1  quit
5000
  • std::defer_lock:延迟获取锁,需要定义对象后手动获取锁,在构造对象时,不会调用mutex的任意lock()函数。
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int32_t gNum = 0;

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        // int32_t i=0;
        // std::this_thread::sleep_for(std::chrono::seconds(3));
        // while (i < 5000)
        // {
        //     gNum++;
        //     i++;
        // }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}


int main()
{

    std::thread t1(increase, 1);
    std::thread t2(increase, 2);

    t1.join();
    t2.join();

    std::cout << gNum << std::endl;

    return 0;
}

设定defer_lock_t之后,定义lock对象的时候不会上锁。

1  increase in
1  get lock fail
1  quit
2  increase in
2  get lock fail
2  quit
0

手动调用lock的版本

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

std::mutex mtx;
int32_t gNum = 0;

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

        lock.lock();
        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}


int main()
{

    std::thread t1(increase, 1);
    std::thread t2(increase, 2);

    t1.join();
    t2.join();

    std::cout << gNum << std::endl;

    return 0;
}

手动调用lock会阻塞执行的线程,直到获取到锁。

1  increase in
1  get lock ok
2  increase in
1  quit
2  get lock ok
2  quit
10000

手动调用try_lock()不会阻塞当前执行的线程,会直接返回结果true/false.

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

        lock.try_lock();
        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
            return;
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}
2  increase in
2  get lock ok
1  increase in
1  get lock fail
2  quit
5000

-std::adopt_lock:可以接管已经上锁的mutex。
如果不设置标签std::defer_lock,在mutex已经被锁定的情况下,直接通过c++ unique_lock<mutex> lock(mtx)的方法再去获取锁,则会导致死锁,程序卡死。

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        if(mtx.try_lock())
        {
            std::cout << id << "  get lock by mtx\n";
        }

        std::unique_lock<std::mutex> lock(mtx); //再次尝试去获取锁,用这种方式会死锁。
        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
            return;
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}
2  increase in
2  get lock by mtx
1  increase in
-->程序卡死在这里,thread 2死锁

使用std::adopt_lock接管被当前线程锁定的锁。

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        mtx.lock();
        std::unique_lock<std::mutex> lock(mtx, std::adopt_lock); //接管锁之后不需要通过mutex.unlock()去手动释放了

        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
            return;
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}

这样就不需要再通过手动unlock去释放mutex的锁了

1  increase in
1  get lock ok
2  increase in
1  quit
2  get lock ok
2  quit
10000

下面的代码是一个存在问题的代码,假设执行顺序是id 1->id 2的情况,thread 2会接管thread 1锁定的锁,造成资源。如果通过std::adopt_lock去接管不是由当前线程锁定的锁(当前线程未进行锁的锁定,或其他线程锁定的锁),活导致未定义行为。

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
    {
        if(mtx.try_lock())
        {
            std::cout << id << "  get lock by mtx\n";
        }

        std::unique_lock<std::mutex> lock(mtx, std::adopt_lock); //再次尝试去获取锁

        if(!lock.owns_lock())
        {
            std::cout << id << "  get lock fail\n";
            return;
        }
        else
        {
            std::cout << id << "  get lock ok\n";           
        }
        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }
    }   //退出作用域后lock自动销毁,锁会释放
    std::cout << id << "  quit\n";
}

使用adop_lock之后,后面再去尝试获取锁的线程会接管前面已经被锁定的锁。

zxy@ubuntu:~/test$ ./lock
1  increase in
1  get lock by mtx
1  get lock ok
2  increase in
2  get lock ok
2  quit
1  quit
10000
zxy@ubuntu:~/test$ ./lock
1  increase in
1  get lock by mtx
1  get lock ok
2  increase in
2  get lock ok   ---->thread 1的锁被接管
2  quit
1  quit
9420            ----->两个线程

try_lock_for和try_lock_until

使用unique_lock的成员函数try_lock_for和try_lock_until,可以实现超时枷锁,在指定时间内没有获取到锁则返回。使用这两个函数时,需要传入timed_mutex,调用函数相当于调用mutex.try_lock_for/mutex.try_lock_until。

std::timed_mutex mtx;
int32_t gNum = 0;

void increase(int32_t id)
{
    std::cout << id << "  increase in\n";
 
    std::unique_lock<std::timed_mutex> lock(mtx, std::defer_lock);
    lock.try_lock_for(std::chrono::seconds(id));
    if(!lock.owns_lock())
    {
        std::cout << id << "  get lock fail\n";
    }
    else
    {
        std::cout << id << "  get lock ok\n";       

        int32_t i=0;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        while (i < 5000)
        {
            gNum++;
            i++;
        }    
    }
    std::cout << id << "  quit\n";
}
1  increase in
1  get lock ok  --->获取锁OK后,总共会sleep 4s
2  increase in
2  get lock fail  --->获取锁最多等待2s,因为此时线程1锁定中,获取锁失败
2  quit
1  quit
5000

shared_lock

  • shard_lock
  • shared_timed_mutex
    shared_lock可以实现共享锁,需要使用shared_timed_mutex。
    shared_lock在构造时会调用shared_timed_mutex的lock_shared()函数,析构时会调用unlock_shared()。

使用场景主要是多个线程同时读取同一个共享资源时,来使用共享锁。

#include <shared_mutex>

std::shared_timed_mutex mtx;
int32_t gNum = 100;

void read(int32_t id)
{
    std::cout << id << "  read in\n";
 
    std::shared_lock<std::shared_timed_mutex> lock(mtx, std::defer_lock);
 
    lock.lock();
    if(!lock.owns_lock())
    {
        std::cout << id << "  get lock fail\n";
    }
    else
    {
        std::cout << id << "  get lock ok\n";       
        std::cout << id << "  gNum = " << gNum << "\n";       
    }
    std::cout << id << "  quit\n";
}
1  read in
1  get lock ok
1  gNum = 100
1  quit
2  read in
2  get lock ok
2  gNum = 100
2  quit
100

shared_lock也可以调用try_lock(),try_lock_for(),try_lock_until()。

#include <shared_mutex>


std::shared_timed_mutex mtx;
int32_t gNum = 100;

void read(int32_t id)
{
    std::cout << id << "  read in\n";
 
    std::shared_lock<std::shared_timed_mutex> lock(mtx, std::defer_lock);
 
    lock.try_lock_for(std::chrono::seconds(id));
    if(!lock.owns_lock())
    {
        std::cout << id << "  get lock fail\n";
    }
    else
    {
        std::cout << id << "  get lock ok\n";       
        std::cout << id << "  gNum = " << gNum << "\n";       
    }
    std::cout << id << "  quit\n";
}
posted @ 2025-04-25 11:25  酸菜馅粘豆包  阅读(28)  评论(0)    收藏  举报