std::thread线程库详解(2)

目录

简介

上一篇博文中,介绍了一下如何创建一个线程,分别是std::threadstd::jthread (C++20)。这两种方法相似,std::jthread相对来说,更加方便一些,具体可以再看看原来的博文,std::thread线程详解(1)

这一次,我将介绍一下,多线程的锁。锁在多线程中是使用非常广泛的。是多线程中最常见的同步方式。主要介绍的锁有mutexrecursive_mutexshared_mutex

最基本的锁 std::mutex

使用

std::mutex是最基本的锁,也是最常见的锁。它提供了最基本的多线程编程同步方法。

using namespace std::chrono_literals;

std::mutex g_mutex;

void thread_func() {
    g_mutex.lock();
    std::cout << "Thread out 1: " << std::this_thread::get_id() << std::endl;;
    std::this_thread::sleep_for(1s);
    std::cout << "Thread out 2: " << std::this_thread::get_id() << std::endl;;
    g_mutex.unlock();
}

int main() {
    std::cout << "Mutex Test." << std::endl;
    std::thread thread1(thread_func);
    std::thread thread2(thread_func);
    thread1.join();
    thread2.join();
    return 0;
}

以上示例中,只有一个线程函数thread_func,它的工作很简单:

首先对g_mutex加锁,然后输出一段字符串,接着休眠1s,输出第二段字符串,最后对g_mutex进行解锁。

输出结果如下:

锁的本质是解决多线程对同一资源竞争读写的问题。这里我们的资源是标准输出std::cout。锁的存在让输出有序,可预测了。

方法和属性

  • lock() 为对象加锁,如果已经被锁了,则阻塞线程;
  • try_lock() 尝试加锁,如果已经被加锁,则返回false,否则将对其进行加锁并返回true;
  • unlock() 为对象解锁,通常和加锁(lock()try_lock())成对出现;
  • native_handle() 返回锁的POSIX标准对象。

递归锁 std::recursive_mutex

std::recursive_mutex是一个递归锁,方法和使用都和std::mutex类似。唯一的不同是,std::mutex在同一时间,只允许加锁一次,而std::revursive_mutex允许同一线程下进行多次加锁。如:

// 定义递归锁
std::recursive_mutex g_mutex;

// 线程函数
void thread_func(int thread_id, int time) {
    g_mutex.lock();
    std::cout << "Thread " << thread_id << ": " << time << std::endl;
    if (time != 0) thread_func(thread_id, time - 1);
    g_mutex.unlock();
}

// 初始化线程
std::thread thread1(thread_func, 1, 3);
std::thread thread2(thread_func, 2, 4);

这一次的方法和之前的略有不同,为了更加直观的观察不同的线程,这次是在输入的时候输入一个标志来区分不同的线程。可以清楚的看到,这是一个递归函数,每次调用的时候都将time减少1,直到其变为0。需要注意的是,在递归的时候并没有释放锁,而是直接进入,因此在第二层遍历的时候,又会对g_mutex进行一次加锁,如果是普通的锁,次数将会阻塞进程,变成死锁。但是此时使用的是递归锁,它允许在同一个线程,多次加锁,因此这个程序可以成功运行,并获得输出。

递归锁的方法和普通锁的方法类似。

共享锁 std::shared_mutex (C++17)

std::shared_mutex在C++14已经存在了,但是在C++14中的std::shared_mutex是带timing的版本的读写锁(也就是说,C++14中的std::shared_mutex等于C++17中的std::shared_timed_mutex)。读写锁有两种加锁的方式,一种是shared_lock(),另一种lock()shared_lock是读模式,而lock是写模式。读写锁允许多个读加锁,而写加锁和其他所有加锁互斥。即同一时间下:

  • 允许多个线程同时读;
  • 只允许一个线程写;
  • 写的时候不允许读,读的时候不允许写。

示例:

// 共享锁
std::shared_mutex g_mutex;

// 读线程 1
void thread_read_1_func(int thread_id) {
    // 第一个获取读权限
    g_mutex.lock_shared();
    std::cout << "Read thread " << thread_id << " out 1." << std::endl;
    // 睡眠2s,等待读线程2,获取读权限,确认可以多个线程进行读加锁
    std::this_thread::sleep_for(2s);
    std::cout << "Read thread " << thread_id << " out 2." << std::endl;
    // 解锁读
    g_mutex.unlock_shared();
}

void thread_read_2_func(int thread_id) {
    // 睡眠500ms,确保读线程1先获取锁
    std::this_thread::sleep_for(500ms);
    g_mutex.lock_shared();
    std::cout << "Read thread " << thread_id << " out 1."  << std::endl;
    std::this_thread::sleep_for(3s);
    std::cout << "Read thread " << thread_id << " out 2."  << std::endl;
    g_mutex.unlock_shared();
}

void thread_write_1_func(int thread_id) {
    // 确保读线程先获得锁,确认读写互斥
    std::this_thread::sleep_for(300ms);
    g_mutex.lock();
    std::cout << "Write thread " << thread_id << " out 1."  << std::endl;
    g_mutex.unlock();
}

其输出为:

带超时的锁

上面介绍的所有的锁,都带有超时版本。即timed_mutexrecursive_timed_mutexshared_timed_mutex。他们使用时,和普通版本类似,不过try_lock方法多了两个超时的版本try_lock_fortry_lock_until。调用这一函数时,如果锁已经被获取了,线程将会阻塞一段时间,如果这一段时间内,获取到了锁则返回true,否则返回false

这里我们只介绍timed_mutex,其他的类似。

void thread_func(int thread_id) {
    if (!g_mutex.try_lock_for(0.5s)) return;
    std::cout << "Thread out 1: " << thread_id << std::endl;;
    std::this_thread::sleep_for(1s);
    std::cout << "Thread out 2: " << thread_id << std::endl;;
    g_mutex.unlock();
    g_mutex.native_handle();
}

其输出为:

可以看到,这里只有一个线程有输出,另一个线程,在等待0.5s后直接退出了(没有获取到锁)。

总结

本文主要介绍了三种不同的锁,普通锁,递归锁,读写锁。三个锁有着不一样的使用方法,但是可以确定的是,过多的使用锁,会导致程序中的串行部分过多,并行效果不好。因此对于锁的使用,需要尽量的克制,尽量的合理。

下一篇文章将介绍锁的管理。

博客原文:https://www.cnblogs.com/ink19/p/std_thread-2.html

给TA买糖
共{{data.count}}人
人已赞赏
经验教程

地图开发笔记(一):百度地图介绍、使用和Qt内嵌地图Demo

2021-1-10 20:34:04

经验教程

让微信小程序开发如鱼得水

2021-1-10 20:35:23

⚠️
免责声明:根据《计算机软件保护条例》第十七条规定“为了学习和研究软件内含的设计思想和原理,通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可,不向其支付报酬。”您需知晓本站所有内容资源均来源于网络,仅供用户交流学习与研究使用,版权归属原版权方所有,版权争议与本站无关,用户本人下载后不能用作商业或非法用途,需在24个小时之内从您的电脑中彻底删除上述内容,否则后果均由用户承担责任;如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途,否则一切后果请您自行承担,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。 本站为个人博客非盈利性站点,所有软件信息均来自网络,所有资源仅供学习参考研究目的,并不贩卖软件,不存在任何商业目的及用途,网站会员捐赠是您喜欢本站而产生的赞助支持行为,仅为维持服务器的开支与维护,全凭自愿无任何强求。本站部份代码及教程来源于互联网,仅供网友学习交流,若您喜欢本文可附上原文链接随意转载。
无意侵害您的权益,请发送邮件至 momeis6@qq.com 或点击右侧 私信:momeis 反馈,我们将尽快处理。
0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索