c++11 mutex互斥量

2023-11-13 16:12:40 浏览数 (2)

1. 什么是mutex?

The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.

C mutex 类是一个简单的同步结构,用于保护共享数据免受从多个线程同时访问,避免数据竞争,并提供线程间的同步支持。其在头文件<mutex>中定义。

mutex类提供的方法主要有:

  • lock:锁定互斥。若另一线程已锁定互斥,则到 lock 的调用将阻塞执行,直至获得锁。若 lock 为已占有 mutex 的线程调用,则行为未定义。
  • try_lock:尝试锁定互斥。立即返回。成功获得锁时返回 true ,否则返回 false 。若已占有 mutex 的线程调用 try_lock ,则行为未定义。
  • unlock:解锁互斥。互斥必须为当前执行线程所锁定,否则行为未定义。

当在多个线程之间对共享数据进行相互独占访问,我们可以创建一个互斥对象,并使用 lock()unlock() 函数使代码的共享数据一次只能用于一个线程。

代码语言:javascript复制
#include<iostream>
#include <mutex>

std::mutex m;

m.lock();

/**********************************/
/*   这一部分代码将成为关键的一部分    */
/*   一次只有一个线程可以访问此部分    */
/**********************************/

m.unlock();

在上面的示例中,我们使用 lockunlock 来包围我们的代码段,并防止它同时从多个线程访问。一旦调用 lock()方法,只有一个线程能够访问关键部分,直到调用 unlock()方法。因此,如果另一个线程希望进入临界部分,则必须等到第一个线程到达 unlock() 后才可以调用访问。

示例:

代码语言:javascript复制
#include <chrono>
#include <mutex>
#include <thread>
#include <iostream> // std::cout
 
std::chrono::milliseconds interval(100);
 
std::mutex mutex;
int job_shared = 0; // 两个线程都能修改 'job_shared',
    // mutex 将保护此变量
 
int job_exclusive = 0; // 只有一个线程能修改 'job_exclusive'
    // 不需要保护
 
// 此线程能修改 'job_shared' 和 'job_exclusive'
void job_1() 
{
    std::this_thread::sleep_for(interval); // 令 'job_2' 持锁
 
    while (true) {
        // 尝试锁定 mutex 以修改 'job_shared'
        if (mutex.try_lock()) {
            std::cout << "job shared (" << job_shared << ")n";
            mutex.unlock();
            return;
        } else {
            // 不能获取锁以修改 'job_shared'
            // 但有其他工作可做
              job_exclusive;
            std::cout << "job exclusive (" << job_exclusive << ")n";
            std::this_thread::sleep_for(interval);
        }
    }
}
 
// 此线程只能修改 'job_shared'
void job_2() 
{
    mutex.lock();
    std::this_thread::sleep_for(5 * interval);
      job_shared;
    mutex.unlock();
}
 
int main() 
{
    std::thread thread_1(job_1);
    std::thread thread_2(job_2);
 
    thread_1.join();
    thread_2.join();
}

可能的输出:

代码语言:javascript复制
job exclusive (1)
job exclusive (2)
job exclusive (3)
job exclusive (4)
job shared (1)

std::mutex 既不可复制亦不可移动。

2.C 11提供的其他互斥量

mutex提供了基本的互斥设施,在此基础上,C 11还提供了以下互斥类:

  • timed_mutex:提供互斥设施,实现有时限锁定。
  • recursive_mutex:提供能被同一线程递归锁定的互斥设施。
  • recursive_timed_mutex:提供能被同一线程递归锁定的互斥设施,并实现有时限锁定。

timed_mutex

timed_mutex 类是能用于保护数据免受多个线程同时访问的同步原语。

其拥有类似 mutex的行为, timed_mutex 提供排他性非递归所有权语义。另外, timed_mutex 提供通过 try_lock_for()try_lock_until()方法试图带时限地要求 timed_mutex 所有权的能力。其提供的方法主要有:

成员函数

作用

lock

锁定互斥,若互斥不可用则阻塞 。

try_lock

尝试锁定互斥,若互斥不可用则返回 。

try_lock_for

尝试锁定互斥,若互斥在指定的时限时期中不可用则返回。

try_lock_until

尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回 。

unlock

解锁互斥。

try_lock_for的函数原型如下:

代码语言:javascript复制
//timeout_duration为要阻塞的最大时长
template< class Rep, class Period >
bool try_lock_for( const std::chrono::duration<Rep, Period>& timeout_duration ); //C  1

当前线程会在锁定成功(占有互斥量)或者经过指定的时长 timeout_duration(超时)前阻塞,取决于何者先到来。锁定成功时返回 true,否则返回 false。如果timeout_duration小于或等于 timeout_duration.zero(),那么函数表现同try_lock()

:由于调度或资源争议延迟,此函数可能阻塞长于 timeout_duration

try_lock_until的函数原型如下:

代码语言:javascript复制
//timeout_time为要阻塞到的最晚时间点
template< class Clock, class Duration >
bool try_lock_until( const std::chrono::time_point<Clock, Duration>& timeout_time ); //C  11 起

当前线程会在锁定成功(占有互斥量)或者抵达指定的时间点 timeout_time(超时)前阻塞,取决于何者先到来。锁定成功时返回 true,否则返回 false。如果调用时已经过了时间点 timeout_time,那么此函数表现同 try_lock()

recursive_mutex

recursive_mutex 类是同步原语,能用于保护共享数据免受从个多线程同时访问。其提供排他性递归所有权语义:

  • 调用方线程在从它成功调用lock或try_lock开始的时期里占有 recursive_mutex 。此时期间,线程可以进行对locktry_lock的附加调用。所有权的时期在线程调用unlock匹配次数时结束。
  • 线程占有 recursive_mutex 时,若其他所有线程试图要求 recursive_mutex 的所有权,则它们将阻塞(对于调用lock)或收到 false 返回值(对于调用try_lock )。
  • 可锁定 recursive_mutex 次数的最大值是未指定的,但抵达该数后,对lock的调用将抛出 std::system_error而对 try_lock的调用将返回 false

其提供的方法与mutex一致,如下所示:

成员函数

作用

lock

锁定互斥,若互斥不可用则阻塞 。

try_lock

尝试锁定互斥,若互斥不可用则返回 。

unlock

解锁互斥。

示例:

代码语言:javascript复制
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
 
int g_num = 0;  // 为 g_num_mutex 所保护
std::mutex g_num_mutex;
 
void slow_increment(int id) 
{
    for (int i = 0; i < 3;   i) {
        g_num_mutex.lock();
          g_num;
        std::cout << id << " => " << g_num << 'n';
        g_num_mutex.unlock();
 
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}
 
int main()
{
    std::thread t1(slow_increment, 0);
    std::thread t2(slow_increment, 1);
    t1.join();
    t2.join();
}

可能的输出:

代码语言:javascript复制
0 => 1
1 => 2
0 => 3
1 => 4
0 => 5
1 => 6

:若 recursive_mutex 在仍为某线程占有时被销毁,则程序行为未定义。

recursive_timed_mutex

recursive_timed_mutex 是同步原语,能用于保护共享数据免受从多个线程同时访问。其以类似 std::recursive_mutex的方式,提供排他性递归所有权语义。另外, recursive_timed_mutex 通过 try_lock_fortry_lock_until方法,提供带时限地试图要求 recursive_timed_mutex 所有权的能力。其提供的方法如下:

成员函数

作用

lock

锁定互斥,若互斥不可用则阻塞 。

try_lock

尝试锁定互斥,若互斥不可用则返回 。

try_lock_for

尝试锁定互斥,若互斥在指定的时限时期中不可用则返回。

try_lock_until

尝试锁定互斥,若直至抵达指定时间点互斥不可用则返回 。

unlock

解锁互斥。

recursive_timed_mutextry_lock_fortimed_mutextry_lock_for类似,都尝试锁定互斥量。当前线程会在锁定成功(占有互斥量)或者经过指定的时长 timeout_duration(超时)前阻塞,取决于何者先到来。锁定成功时返回 true,否则返回 false

同样的,recursive_timed_mutextry_lock_untiltimed_mutextry_lock_until类似,都尝试锁定互斥量。当前线程会在锁定成功(占有互斥量)或者抵达指定的时间点 timeout_time(超时)前阻塞,取决于何者先到来。锁定成功时返回 true,否则返回 false

3.总结

在共享资源且不希望它们同时被多个或多个线程修改的情况下我们应该使用互斥量保证我们数据的安全和有序。通过使用互斥量,我们可以锁定包含应用程序关键逻辑的对象。这也防止了数据不一致,这在实时应用程序中非常重要。同时,我们在使用lock的时候一定要记得unlock,否则会造成死锁,后面我们也将会继续介绍C 11中unique_locklock_guard可以避免死锁问题。

0 人点赞