目录

简介

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

这一次,我将介绍一下,多线程的锁。锁在多线程中是使用非常广泛的。是多线程中最常见的同步方式。主要介绍的锁有mutex,recursive_mutex, shared_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_mutex,recursive_timed_mutex,shared_timed_mutex。他们使用时,和普通版本类似,不过try_lock方法多了两个超时的版本try_lock_for和try_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

std::thread线程库详解(2)的更多相关文章

  1. std::thread线程库详解(3)

    目录 目录 前言 lock_guard scoped_lock (C++17) unique_lock shared_lock 总结 ref 前言 前两篇的博文分别介绍了标准库里面的线程和锁,这一次的 ...

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

    目录 目录 前言 信号量 counting_semaphore latch与barrier latch barrier 总结 前言 前面四部分内容已经把目前常用的C++标准库中线程库的一些同步库介绍完 ...

  3. std::thread线程库详解(4)

    目录 目录 前言 条件变量 一些需要注意的地方 总结 前言 本文主要介绍了多线程中的条件变量,条件变量在多线程同步中用的也比较多.我第一次接触到条件变量的时候是在完成一个多线程队列的时候.条件变量用在 ...

  4. Java Thread(线程)案例详解sleep和wait的区别

    上次对Java Thread有了总体的概述与总结,当然大多都是理论上的,这次我将详解Thread中两个常用且容易疑惑的方法.并通过实例代码进行解疑... F区别 sleep()方法 sleep()使当 ...

  5. Thread线程相关方法详解

    1.sleep() 使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁.也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据.注意该方 ...

  6. “全栈2019”Java多线程第十章:Thread.State线程状态详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. Lua的协程和协程库详解

    我们首先介绍一下什么是协程.然后详细介绍一下coroutine库,然后介绍一下协程的简单用法,最后介绍一下协程的复杂用法. 一.协程是什么? (1)线程 首先复习一下多线程.我们都知道线程——Thre ...

  8. Python--urllib3库详解1

    Python--urllib3库详解1 Urllib3是一个功能强大,条理清晰,用于HTTP客户端的Python库,许多Python的原生系统已经开始使用urllib3.Urllib3提供了很多pyt ...

  9. MySQL5.6的4个自带库详解

    MySQL5.6的4个自带库详解 1.information_schema详细介绍: information_schema数据库是MySQL自带的,它提供了访问数据库元数据的方式.什么是元数据呢?元数 ...

随机推荐

  1. Asp.NetCore之AutoMapper进阶篇

    应用场景 在上一篇文章--Asp.NetCore之AutoMapper基础篇中我们简单介绍了一些AutoMapper的基础用法以及如何在.NetCore中实现快速开发.我相信用过AutoMapper实 ...

  2. WPF中Logical Tree和Visual Tree的区别

    The Logical TreeThe logical tree describes the relations between elements of the user interface. The ...

  3. 题解-Infinite Path

    题解-Infinite Path \(\color{#9933cc}{\texttt{Infinite Path}}\) \(T\) 组测试数据.每次给你一个 \(n\) 的排列 \(\{p_n\}\ ...

  4. 【MindSpore】Ubuntu16.04上成功安装GPU版MindSpore1.0.1

    本文是在宿主机Ubuntu16.04上拉取cuda10.1-cudnn7-ubuntu18.04的镜像,在容器中通过Miniconda3创建python3.7.5的环境并成功安装mindspore_g ...

  5. AWT05-对话框

    1.Dialog Dialog组件是Window的子类,是容器类,是特殊组件. Dialog是可以独立存在的顶级窗口,使用上和普通窗口几乎没有区别,但应注意以下两点: 1.对话框通常依赖于其他窗口,也 ...

  6. html+css一些简单案例:爱心点击,盒子模型,2d动画

    canvas绘制爱心 效果预览 上代码 <!doctype html> <html> <head> <title>HTML5 Canvas爱心飘动动画特 ...

  7. 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  8. 用Python批量裁取图,来获取文件夹中所有图片名

    批量截图(截取正方形图,哪个边短就用哪个边作为标准来截取) 功能是裁取图片中红色框的部分. 代码为: import sys from tkinter.tix import Tk from PIL im ...

  9. Autofac的基本使用---3、泛型类型

    Autofac的基本使用---目录 准备 使用的表是Teacher,创建相关的IDAL.DAL.IBLL.BLL层. 使用EF,创建一个Model层,存放edmx文件. 创建一个Infrastruct ...

  10. [EF] - Entity Framework 6处理User Defined Function(UDF SQL Server)

    随着EF5的发布,新增了对数据库(SQL Server) UDF的支持,具体可以看以下的连接:https://msdn.microsoft.com/en-us/data/hh859577.aspx,新 ...