「C++ 多线程」std::timed_mutex 基本用法

文章大图来源:pixiv_id=52826716

1 std::timed_mutex 基本概念

std::timed_mutex 是自 C++ 11 引入的互斥量(mutex)类型,用于 C++ 多线程编程中控制对共享数据资源的并发互斥访问。std::timed_mutex 是独占且非递归的。

std::timed_mutex 包含了 std::mutex 的基本功能,包括 lock()unlock() 函数进行加锁和解锁、try_lock() 以非阻塞的方式尝试获得锁等。除此以外,std::timed_mutex 还提供了成员函数 try_lock_for()try_lock_until,用于支持 超时放弃 的尝试获得锁的机制,在避免无限等待、避免出现死锁等方面有着中重要作用。

2 std::timed_mutex::try_lock_for()

std::timed_mutex::try_lock_for() 用于尝试在指定的时间期限内获取互斥量的锁。这个函数接受一个 std::chrono::duration 类型的参数,表示获取锁的最长等待时间。如果在这个时间期限内获得了锁,函数返回 true,线程继续执行下去;否则,函数返回 false,线程会采取超时放弃的策略,直接返回。

std::timed_mutex::try_lock_for() 接受的参数接近 0 时,那么函数的功能基本就等于 std::timed_mutex::try_lock()

这个函数所造成的等待(阻塞)时间可能会略微超过设置的最长等待时间,主要原因在于会有调度或资源争用的延迟。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

std::timed_mutex t_mtx;
int count;

void foo(int id){
// 最长等待时间期限设置为 100 ms
std::chrono::milliseconds timeout(100);
auto start = std::chrono::steady_clock::now();
if(t_mtx.try_lock_for(timeout)){ // 100ms 期限内成功获得锁
// 打印获得锁的等待时间(可能略超 100ms)
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double, std::milli> dura = end - start;
std::cout << "thread " << id << ": Successfully get the lock. ";
std::cout << "waiting duration = " << dura.count() << "ms." << "\n";

// 以下模拟一些互斥操作
std::this_thread::sleep_for(std::chrono::milliseconds(15));
count ++ ; // 对共享数据变量进行访问和修改
t_mtx.unlock(); // 解锁
}
}

int main(){
std::vector<std::thread> threads(10);
for(size_t i = 0; i < threads.size(); i ++ ){
threads[i] = std::thread(foo, i);
}
for(auto& t : threads) t.join();

std::cout << "final count = " << count << "\n";

return 0;
}

在上面的代码中,创建了 10 个线程。在线程函数 foo() 中,设置最长等待时间为 100ms,并且借助了 std::chrono::steady_clock::now() 来进行线程阻塞时长的记录。如果线程成功在等待的时间期限内获得了锁,那么会继续执行下去,模拟一些互斥操作(休息了 15ms),在作用域结束时手动进行解锁。

可能的运行结果如下:

可以看出,成功获得锁的线程等待时长基本都在期限 100ms 内(也有可能运行出来会有个别线程等待时长略超 100ms)。在我的电脑上最终能够成功获得锁的线程大致在 5 ~ 7 个,表明每个线程执行函数的时间大致在 20ms 左右(模拟一些互斥操作的 15ms 加上一些其他操作消耗的时间)。

3 std::timed_mutex::try_lock_until()

std::timed_mutex::try_lock_until() 用于尝试在指定时间之前获取互斥量的锁,其接受的是一个 std::chrono::time_point 类型的参数,表示等待获取锁的截止时间。

try_lock_for() 一样,如果在这个时间期限内获得了锁,std::timed_mutex::try_lock_until() 函数返回 true,线程继续执行下去;否则,函数返回 false,线程会采取超时放弃的策略,直接返回。

以下是一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

std::timed_mutex t_mtx;
int count;

void foo(int id){
// 最长等待时间期限设置为 100 ms
std::chrono::milliseconds timeout(100);
auto start = std::chrono::steady_clock::now();
// 此 if 判断和 if(t_mtx.try_lock_for(timeout)) 效果几乎一样
if(t_mtx.try_lock_until(start + timeout)){ // 100ms 期限内成功获得锁
// 打印获得锁的等待时间(可能略超 100ms)
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double, std::milli> dura = end - start;
std::cout << "thread " << id << ": Successfully get the lock. ";
std::cout << "waiting duration = " << dura.count() << "ms." << "\n";

// 以下模拟一些互斥操作
std::this_thread::sleep_for(std::chrono::milliseconds(15));
count ++ ; // 对共享数据变量进行访问和修改
t_mtx.unlock(); // 解锁
}
}

int main(){
std::vector<std::thread> threads(10);
for(size_t i = 0; i < threads.size(); i ++ ){
threads[i] = std::thread(foo, i);
}
for(auto& t : threads) t.join();

std::cout << "final count = " << count << "\n";

return 0;
}

在上面的代码的线程函数 foo() 中,设置等待的时间截止点为 std::chrono::steady_clock::now() 加上 100ms 的时刻(相当于设置最长等待时间为 100ms)。并且我们用 std::chrono::steady_clock::now() 来进行线程阻塞时长的记录。如果线程成功在等待的时间期限内获得了锁,那么会继续执行下去,模拟一些互斥操作(休息了 15ms),在作用域结束时手动进行解锁。

可能的运行结果如下:

参考

  1. cppreference.com std::timed_mutex::try_lock_for()

  2. cppreference.com std::timed_mutex::try_lock_until()


「C++ 多线程」std::timed_mutex 基本用法
https://marisamagic.github.io/2024/12/28/20241228/
作者
MarisaMagic
发布于
2024年12月28日
许可协议