并发支持库(3)-互斥

互斥可以对操作集合上锁,避免多个线程同时访问共享资源。这些操作集合可以看作是一次原子操作。

本文章的代码库:

https://gitee.com/gamestorm577/CppStd

1. 互斥类

c++提供了各种互斥类。

mutex

构造函数

默认构造函数构造一个处于未锁定状态的互斥

锁定

mutex提供lock和try_lock接口用于锁定互斥,unlock用于解锁互斥。

1.调用线程调用lock时,如果mutex为未锁定状态,那么调用线程锁住该互斥;如果mutex为锁定状态,那么调用线程阻塞,知道mutex被解锁。

2.调用线程调用try_lock尝试锁定mutex并立即返回。如果mutex为未锁定状态,那么try_lock返回true并锁住mutex;如果mutex为锁定状态,try_lock返回false。

3.调用线程调用unlock解锁互斥。

4.调用线程调用lock或者try_lock时必须不占有mutex,调用unlock时mutex必须被调用线程锁定,否则会出现未定义行为。

代码示例:

int num = 0;
std::mutex mutex;

auto Func = [&](int id)
{
    for (int i = 0; i < 5; ++i)
    {
        mutex.lock();
        ++num;
        std::cout << id << ": " << num << std::endl;
        mutex.unlock();

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
};

std::thread t1(Func, 0);
std::thread t2(Func, 1);
t1.join();
t2.join();

可能的输出结果:

0: 1
1: 2
0: 3
1: 4
0: 5
1: 6
0: 7
1: 8
1: 9
0: 10

native_handle

返回mutex的底层实现句柄

timed_mutex

相比mutex,timed_mutex可以实现指定时间的锁定,对应的接口为try_lock_for和try_lock_until。

recursive_mutex

对于mutex,当某个执行线程已经占用mutex的情况下不能再调用lock或者try_lock,而recursive_mutex可以去掉这个限制。注意在占用mutex的情况下,调用recursive_mutex的lock或者try_lock也是有次数限制的。

recursive_time_mutex

和recursive_mutex的功能相同,同时增加了指定时间的锁定功能。

shared_mutex

shared_mutex有两个访问级别:共享性和独占性。如果一个执行线程获取了shared_mutex的独占行,那么其他线程无法获取该锁。当没有任何线程获取shared_mutex的独占性时,多个线程可以同时获取shared_mutex的共享性。

对于一些共享资源读取远多于写入操作的场景,shared_mutex可以提高并发性能。

shared_mutex的lock、try_lock接口用于获取独占性,unlock用于解锁独占性。lock_shared、try_lock_shared接口口用于获取共享性,unlock_shared用于解锁共享性。

代码示例:

auto Timer = [](std::function<void()> func) -> void
{
    auto start = std::chrono::system_clock::now();
    func();
    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double, std::milli> time = end - start;
    std::cout << "use time: " << time.count() << std::endl;
};

struct Test1
{
    void Func()
    {
        m_Mut.lock();
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        m_Mut.unlock();
    }

    std::mutex m_Mut;
};

struct Test2
{
    void Func()
    {
        m_Mut.lock_shared();
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        m_Mut.unlock_shared();
    }

    std::shared_mutex m_Mut;
};

auto Func1 = []()
{
    Test1 test1;
    std::vector<std::thread> threads(20);

    for (auto& t : threads)
    {
        t = std::thread(&Test1::Func, &test1);
    }

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

auto Func2 = []()
{
    Test2 test2;
    std::vector<std::thread> threads(20);

    for (auto& t : threads)
    {
        t = std::thread(&Test2::Func, &test2);
    }

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

Timer(Func1);
Timer(Func2);

输出结果:

use time: 326
use time: 33.2421

shared_timed_mutex

shared_timed_mutex和shared_mutex的功能相同,并且支持指定时间的锁定

2. lock类

c++提供了一些lock类用于方便地管理互斥类

lock_guard

lock_guard提供互斥体的RAII风格的包装。当创建lock_guard对象时,它试图占有给定互斥的所有权,当lock_guard离开作用域时,lock_guard被销毁并释放互斥体。代码示例:

struct MyMutex
{
    void lock()
    {
        std::cout << "MyMutex lock" << std::endl;
    }

    void unlock()
    {
        std::cout << "MyMutex unlock" << std::endl;
    }
};

MyMutex mut;
std::lock_guard<MyMutex> lock(mut);

输出结果:

MyMutex lock
MyMutex unlock

scoped_lock

scoped_guard提供多个互斥体的RAII风格的包装。代码示例:

struct MyMutex
{
    void lock()
    {
        std::cout << "MyMutex lock: " << Id << std::endl;
    }

    bool try_lock()
    {
        std::cout << "MyMutex try_lock: " << Id << std::endl;
        return true;
    }

    void unlock()
    {
        std::cout << "MyMutex unlock: " << Id << std::endl;
    }

    int Id = 0;
};

MyMutex mut1;
MyMutex mut2;
MyMutex mut3;
mut1.Id = 3;
mut2.Id = 17;
mut3.Id = 29;

std::scoped_lock lock(mut1, mut2, mut3);

输出结果:

MyMutex lock: 3
MyMutex try_lock: 17
MyMutex try_lock: 29
MyMutex unlock: 3
MyMutex unlock: 17
MyMutex unlock: 29

unique_lock

unique_lock是一个通用的互斥包装器。

shared_lock

shared_lock是一个通用的共享互斥包装器。

3. 通用锁定算法

c++提供了std::try_lock、std::lock用于锁定互斥

4. 单次调用

c++提供了接口std::call_once来确保函数只被调用一次,无论是单线程还是多线程。代码示例:

struct Test
{
    void Func()
    {
        std::cout << "call Func: " << Id << std::endl;
    }

    void CallFuncOnce()
    {
        std::call_once(flag, &Test::Func, this);
    }

    std::once_flag flag;
    int Id = 0;
};

Test test1;
Test test2;
test1.Id = 3;
test2.Id = 17;

test1.CallFuncOnce();
test1.CallFuncOnce();
test1.CallFuncOnce();
test1.CallFuncOnce();

std::jthread t1(&Test::CallFuncOnce, &test2);
std::jthread t2(&Test::CallFuncOnce, &test2);
std::jthread t3(&Test::CallFuncOnce, &test2);

输出结果:

call Func: 3
call Func: 17

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值