

std::thread线程库详解(2)
source link: http://www.cnblogs.com/ink19/p/std_thread-2.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

目录
- 最基本的锁 std::mutex
- 递归锁 std::recursive_mutex
- 共享锁 std::shared_mutex (C++17)
简介
上一篇博文中,介绍了一下如何创建一个线程,分别是 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后直接退出了(没有获取到锁)。
总结
本文主要介绍了三种不同的锁,普通锁,递归锁,读写锁。三个锁有着不一样的使用方法,但是可以确定的是,过多的使用锁,会导致程序中的串行部分过多,并行效果不好。因此对于锁的使用,需要尽量的克制,尽量的合理。
下一篇文章将介绍锁的管理。
Recommend
-
63
深入理解Thread线程和Queue队列 游星啊· 2018-05-18...
-
26
redis io thread多线程的性能瓶颈? – 峰云就她了
-
36
C++11中提供了异步线程接口std::async,std::async是异步编程的高级封装,相对于直接使用std::thread,std::async的优势在于: 1、std::async会自动创建线程去调用线程函数,相对于低层次的std::thread,使用起来非常方便;
-
9
iOS开发-多线程编程技术(Thread、Cocoa operations、GCD) 简介...
-
5
C++ 标准库 std::thread 的用法和实现 作者: 张志强 ...
-
41
C++ 设置 std::thread 线程名称 作者: 张志强 ...
-
8
不建议使用std::thread 2020年3月15日 | 字数 1746 |
-
6
Thread.sleep(0)的意义& 多线程详解 作者:博客园 2022-08-29 10:52:37 系统 我们可能经常会用到 Thread.Sleep 函数来使线程挂起一段时间。那...
-
10
详解JAVA线程问题诊断工具Thread Dump
-
5
在之前的文章中,我们简单的介绍了线程诞生的意义和基本概念,采用多线程的编程方式,能充分利用 CPU 资源,显著的提升程序的执行效率。 其中java.lang.Thread是 Java 实现多线程编程最核心的类,学习Thread类中的方法,是学习多线...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK