C++并发与多线程 12_recursive_mutextimed_mutexrecursive_timed_mutex

Posted TianSong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++并发与多线程 12_recursive_mutextimed_mutexrecursive_timed_mutex相关的知识,希望对你有一定的参考价值。

window 临界区

  • window 临界区资源对象与C++的 std::mutex 对象类似,可以保护多个线程对临界区资源的访问。
#include <iostream>
#include <thread>
#include <Windows.h>

static CRITICAL_SECTION g_winsec;

void print_block (int n, char c)
{
  EnterCriticalSection(&g_winsec);      // 2. 进入临界区

  for (int i=0; i<n; ++i) {
      std::cout << c;
  }

  std::cout << \'\\n\';

  LeaveCriticalSection(&g_winsec);      // 3. 离开临界区
}

int main ()
{
  InitializeCriticalSection(&g_winsec); // 1. 初始化临界资源对象

  std::thread th1 (print_block,50,\'*\');
  std::thread th2 (print_block,50,\'$\');

  th1.join();
  th2.join();

  return 0;
}

输出:

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

多次进入临界区实验

  • window 临界资源对象可以在同一线程中多次重复进入,对应次数的离开,程序仍正常执行。
  • std::mutex 对象只能在同一线程进行一次加锁并对应一次解锁,否则程序抛出异常。

测试1:window 临界区

#include <iostream>
#include <thread>
#include <Windows.h>

static CRITICAL_SECTION g_winsec;

void print_block (int n, char c)
{
  EnterCriticalSection(&g_winsec);      // 2. 进入临界区
  EnterCriticalSection(&g_winsec);      // 多次进入 。。。
  EnterCriticalSection(&g_winsec);      // 多次进入 。。。

  for (int i=0; i<n; ++i) {
      std::cout << c;
  }

  std::cout << \'\\n\';

  LeaveCriticalSection(&g_winsec);      // 3. 离开临界区
  LeaveCriticalSection(&g_winsec);      // 多次离开 。。。
  LeaveCriticalSection(&g_winsec);      // 多次离开 。。。
}

int main ()
{
  InitializeCriticalSection(&g_winsec); // 1. 初始化临界资源对象

  std::thread th1 (print_block,50,\'*\');
  std::thread th2 (print_block,50,\'$\');

  th1.join();
  th2.join();

  return 0;
}

输出:[结果正确]

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

测试2:std::mutex

// mutex example
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
  // critical section (exclusive access to std::cout signaled by locking mtx):
  mtx.lock();
  mtx.lock();
  mtx.lock();
  for (int i=0; i<n; ++i) { std::cout << c; }
  std::cout << \'\\n\';
  mtx.unlock();
  mtx.unlock();
  mtx.unlock();
}

int main ()
{
  std::thread th1 (print_block,50,\'*\');
  std::thread th2 (print_block,50,\'$\');

  th1.join();
  th2.join();

  return 0;
}

输出:

程序异常退出

自动析构技术

  • RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。
  • C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
#include <iostream>
#include <thread>
#include <Windows.h>

static CRITICAL_SECTION g_winsec;

class CWinLock {
public:
    CWinLock(CRITICAL_SECTION *winsec) : m_winsec(winsec)
    {
         EnterCriticalSection(m_winsec); // 进入临界区
    }

    ~CWinLock()
    {
        LeaveCriticalSection(m_winsec);  // 离开临界区
    }

private:
    CRITICAL_SECTION *m_winsec = nullptr;
};

void print_block (int n, char c)
{
  CWinLock win_lock(&g_winsec);

  for (int i=0; i<n; ++i) {
      std::cout << c;
  }

  std::cout << \'\\n\';
}

int main ()
{
  InitializeCriticalSection(&g_winsec); // 1. 初始化临界资源对象

  std::thread th1 (print_block,50,\'*\');
  std::thread th2 (print_block,50,\'$\');

  th1.join();
  th2.join();

  return 0;
}

输出:

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

std::recursive_mutex

  • 就像互斥锁(mutex)一样,递归互斥锁(recursive_mutex)是可锁定的对象,但它允许同一线程获得对互斥锁对象的多级所有权(多次lock)。
  • 这允许从已经锁定它的线程锁定(或尝试锁定)互斥对象,从而获得对互斥对象的新所有权级别:互斥对象实际上将保持对该线程的锁定,直到调用其成员 unlock 的次数与此所有权级别的次数相同。
try_lock如果没有被其它线程锁定,则锁定互斥锁
unlock解锁互斥锁

测试:仅演示说明,使用 recursive_mutex 时需考虑是否存在优化空间!

#include <iostream>
#include <thread>
#include <mutex> 

std::recursive_mutex mtx;           

void print_block (int n, char c) {
  mtx.lock();
  mtx.lock();
  mtx.lock();
  
  for (int i=0; i<n; ++i) { std::cout << c; }
  std::cout << \'\\n\';
  
  mtx.unlock();
  mtx.unlock();
  mtx.unlock();
}

int main ()
{
  std::thread th1 (print_block,50,\'*\');
  std::thread th2 (print_block,50,\'$\');

  th1.join();
  th2.join();

  return 0;
}

输出:

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

std::timed_mutex、std::recursive_timed_mutex

std::timed_mutex

  • 定时互斥锁是一个可时间锁定的对象,旨在通知何时关键代码需要独占访问,就像常规互斥锁一样,但还支持定时尝试锁定请求。
lock调用线程将锁定timed_mutex,并在必要时进行阻塞(其行为与 mutex 完全相同)
try_lock尝试锁定 timed_mutex,而不进行阻塞(其行为与互斥锁完全相同)
try_lock_for尝试锁定 timed_mutex, 最多阻塞 rel_time 时间
try_lock_until尝试锁定 timed_mutex,最多阻塞到 abs_time 时间点
unlock解锁 timed_mutex,释放对其的所有权(其行为与 mutex 相同)

测试1: try_lock_for

// timed_mutex::try_lock_for example
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex

std::timed_mutex mtx;

void fireworks () {
  // waiting to get a lock: each thread prints "-" every 200ms:
  while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
    std::cout << "-";
  }
  // got a lock! - wait for 1s, then this thread prints "*"
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  std::cout << "*\\n";
  mtx.unlock();
}

int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(fireworks);

  for (auto& th : threads) th.join();

  return 0;
}

输出:

------------------------------------*
----------------------------------------*
-----------------------------------*
------------------------*
-------------------------*
--------------------*
---------------*
--------*
-----*
*

测试2:try_lock_until

// timed_mutex::try_lock_until example
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::system_clock
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex
#include <ctime>          // std::time_t, std::tm, std::localtime, std::mktime

std::timed_mutex cinderella;

// gets time_point for next midnight:
std::chrono::time_point<std::chrono::system_clock> midnight() {
  using std::chrono::system_clock;
  std::time_t tt = system_clock::to_time_t (system_clock::now());
  struct std::tm * ptm = std::localtime(&tt);
  ++ptm->tm_mday; ptm->tm_hour=0; ptm->tm_min=0; ptm->tm_sec=0;
  return system_clock::from_time_t (mktime(ptm));
}

void carriage() {
  if (cinderella.try_lock_until(midnight())) {
    std::cout << "ride back home on carriage\\n";
    cinderella.unlock();
  }
  else
    std::cout << "carriage reverts to pumpkin\\n";
}

void ball() {
  cinderella.lock();
  std::cout << "at the ball...\\n";
  cinderella.unlock();
}

int main ()
{
  std::thread th1 (ball);
  std::thread th2 (carriage);

  th1.join();
  th2.join();

  return 0;
}

输出:

at the ball...
ride back home on carriage

std::recursive_timed_mutex

  • 递归定时互斥锁将 recursive_timed 和 timed_mutex 的功能结合到一个类中:它既支持通过单个线程获取多个锁定级别又支持定时的 try_lock 请求。
  • 成员函数与 timed_mutex 相同。

以上是关于C++并发与多线程 12_recursive_mutextimed_mutexrecursive_timed_mutex的主要内容,如果未能解决你的问题,请参考以下文章

C++并发与多线程 10_shared_futureautomic

C++并发与多线程 4_创建多个线程数据共享问题分析

C++并发与多线程 9_asyncfuturepackaged_taskpromise

C++并发与多线程 3_线程传参数详解,detach 注意事项

C++并发与多线程 2_线程启动结束,创建线程多种方法,join,detach

C++并发与多线程 11_std::atomic叙谈std::launch(std::async) 深入