C++11 多线程 std::mutex篇 (四).

Mutex

Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文件中,所以如果你需要使用 std::mutex,就必须包含 <mutex> 头文件。

Mutex:

std::mutex 基本互斥类.

std::recursive_mutex 递归互斥类.

std::time_mutex 定时互斥类.

std::recursive_timed_mutex 定时递归互斥类.

Locks:

std::lock_guard  给互斥类上锁。

std::unique_lock 提供了更好的上锁解锁.


Other types:

std::onece_flag

std::adopt_lock_t

std::defer_lock_t

std::try_to_lock_t

 

Functions:

std::try_lock

std::lock

std::call_once

 

std::mutex

 std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。

构造函数:

constexpr mutex() noexcept;
mutex (const mutex&) = delete;

拷贝构造函数是被删除了的,因此是不能对他进行拷贝和std::move拷贝.初始状态的mutex对象是处于unlocked的.

 

成员函数:

void lock();

不接受任何参数也不返回任何类型.

其重要作用是:在当前线程正在访问内存中数据的时候,别的线程是无权访问的.除非等到解锁(unlock).

在当前线程中调用该函数会锁住该互斥量.但是会产生3种情况:

(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。

(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。

(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

 // mutex::lock/unlock
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex
std::mutex mtx;           // mutex for critical section
//加了线程锁,在当前线程访问该数据的时候别的线程是不能访问的. 
void print_thread_id (int id) {
  // critical section (exclusive access to std::cout signaled by locking mtx):
  mtx.lock();
  std::cout << "thread #" << id << '\n';
  mtx.unlock();
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_thread_id,i+1);
  for (auto& th : threads) th.join();
  return 0;
}

 

成员函数:

bool try_lock();

try_lock(),尝试锁住互斥量,如果当前线程成功锁住该互斥量则返回true,否则返回false,如果当前线程使用的互斥量已经被锁住了再次调用该函数也不会导致当前线程阻塞.

但是会产生一下三种情况:

1, 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到当前线程调用 unlock 解锁互斥量.返回true.

2,如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而当前线程并不会被阻塞(blocking)掉.

3, 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock).

 // mutex::try_lock example
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex
volatile int counter (0); // non-atomic counter
std::mutex mtx;           // locks access to counter
void attempt_10k_increases (const int& id_) {
  for (int i=0; i<10000; ++i) {
    if (mtx.try_lock()) {   // only increase if currently not locked:
      ++counter;            //只有在当前线程没有锁住mtx的时候才会增加counter; 
      if(i == 0){
       std::cout<<"thread----id: "<<id_<<'\n';  
      }
      mtx.unlock();
    }
  }
  
  std::cout<<"thrad#: "<<id_<<'\n';
}
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(attempt_10k_increases, i);
  for (auto& th : threads) th.join();
  std::cout << counter << " successful increases of the counter.\n";
  return 0;
}

 成员函数:

void unlock();

释放当前线程对该互斥量的所有权.

 

成员函数:

native_handle_type native_handle();

这个函数比较特殊.具体什么用。。。不晓得啊啊啊啊啊啊。

这个函数好像在mutex中只是给出了声明,我们可以自己实现?

 

 

std::recursive_mutex

std::recursive_mutex 与 std::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

 

std::timed_mutex

std::timed_mutex相对于std::mutex来说多了两个成员函数:try_lock_for()try_lock_until()

template <class Rep, class Period>
  bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);

try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞(blocking)住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁并返回true,如果超时(即在指定时间内还是没有获得锁),则返回 false.

 #include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
std::timed_mutex mtx;
void fireworks(const int& id_)
{
 //设置200毫秒的时间等待时间等待当前线程获得一个互斥量的锁.
 //如果在这200毫秒内当前线程没有获得锁(该互斥量被其他的线程上锁了). 
 //那么一直返回false便会打印出来'-'. 
  
 while( !mtx.try_lock_for(std::chrono::milliseconds(200)) ){
  std::cout<<"-";
 }
 
 std::this_thread::sleep_for(std::chrono::seconds(1));
 std::cout<<"*\n";
 
 mtx.unlock();
}
int main()
{
 std::thread threads[10];
 for(int i=0; i<10; ++i){
  threads[i] = std::thread(fireworks, i);
 }
 
 for(int j=0; j<10; ++j){
  threads[j].join();
 }
 
 return 0;
}

 try_lock_until 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false

 

std::lock_guard

template <class Mutex> class lock_guard;

 lock_guard保证在他(lock_guard)的生命周期内总互斥量总是被锁的,在lock_guard的生命周期结束后互斥量就会被解锁.纵然是在当前线程执行的过程中throw了异常通过lock_guard管理的对象也能解锁互斥量.

ock_guard 对象通常用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁(注:类似 shared_ptr 等智能指针管理动态分配的内存资源 )。

模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型,标准库中定义几种基本的 BasicLockable 类型,分别 std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex, (需要注意的是满足BasicLockable类型的互斥量只需要满足lock和unlock操作即可).

 

构造函数:

explicit lock_guard (mutex_type& m);
lock_guard (mutex_type& m, adopt_lock_t tag); //会在介绍完lock_guard和unique_lock后介绍.
lock_guard (const lock_guard&) = delete;

(1): 管理mutex_type类型对象m,在构造的时候对m上锁(lock).如果m已经被其他线程上锁了,那么先阻塞(blocking)当前线程.

(2):管理mutex_tyoe类型对象m,与1不同的是m对象已经被当前线程上锁了.

(3):拷贝构造由于是被delete的,所以lock_guard是不能使用拷贝构造和移动构造的.(这里特别需要指出的是如果我们自己定义了该类型构造函数编译器就不会再为我们合成了,因此只需要delete掉copy构造函数,move构造函数也就等于被删除掉了)

 #include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_id(const int& id_)
{
 mtx.lock(); //在通过lock_guard对mtx上锁前已经对lock上锁了. 
 std::lock_guard<std::mutex> lg(mtx, std::adopt_lock); //这里lock_guard接手mtx的管理权。在lg的生命周期结束后会自动解锁.
 //这里这里的第二个参数std::adopt_lock,标明该互斥量之前已经被上锁过了. 
 std::cout<<"thread#: "<<id_<<'\n';
}
int main()
{
 std::thread threads[10];
 for(int i=0; i<10; ++i){
  threads[i] = std::thread(print_id, i);
 }
 
 for(int i=0; i<10; ++i){
  threads[i].join();
 }
 
 return 0;
}

 

 std::unique_lock

unique_lock能够提供更好的上锁和解锁控制.与lock_guard的不同之处在于,unique_lock在管理mutex对象的时候拥有绝对的独立权,不可能存在两个unique_lock对象同时管理一个mutex.

 

得注意的是,unique_lock 对象同样也不负责管理 Mutex 对象的生命周期,unique_lock 对象只是简化了 Mutex 对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个 unique_lock 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 unique_lock 的生命周期结束之后,它所管理的锁对象会被解锁,这一点和 lock_guard 类似,但 unique_lock 给程序员提供了更多的自由,我会在下面的内容中给大家介绍 unique_lock 的用法。

另外,与 lock_guard 一样,模板参数 Mutex 代表互斥量类型,例如 std::mutex 类型,它应该是一个基本的 BasicLockable 类型,标准库中定义几种基本的 BasicLockable 类型,分别 std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex (以上四种类型均已在上一篇博客中介绍)以及 std::unique_lock。注:BasicLockable 类型的对象只需满足两种操作,lock 和 unlock,另外还有 Lockable 类型,在 BasicLockable 类型的基础上新增了 try_lock 操作,因此一个满足 Lockable 的对象应支持三种操作:lock,unlock 和 try_lock;最后还有一种 TimedLockable 对象,在 Lockable 类型的基础上又新增了 try_lock_for 和 try_lock_until 两种操作,因此一个满足 TimedLockable 的对象应支持五种操作:lock, unlock, try_lock, try_lock_for, try_lock_until)。

unique_lock() noexcept;
explicit unique_lock (mutex_type& m); //这里用的是operator=,mutex的拷贝构造函数是delete的.
unique_lock (mutex_type& m, try_to_lock_t tag);
unique_lock (mutex_type& m, defer_lock_t tag) noexcept
unique_lock (mutex_type& m, adopt_lock_t tag);
template <class Rep, class Period>
unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
unique_lock (const unique_lock&) = delete;
unique_lock (unique_lock&& x);

(1)默认构造函数:

构建一个unique_lock对象不管理任何对象.

(2):

新创建的unique_lock对象尝试管理(lock)m,如果m已经被其他线程上锁了,那么阻塞当前线程等待其他线程解锁m,再接手管理,

(3):

新建的unique_lock对象尝试调用m的try_lock()对m进行上锁.如果当前线程没有上锁成功,不会被阻塞(blocking)等待其他线程解锁了m,再次接手才往下面执行.

而是接着执行当前线程内的其他内容.

(4):

新建的unique_lock对象并没有锁住m, m应该是被其他线程锁住了.

(5):

在创建unique_lock对象指向,m已经被当前线程锁住了,unique_lock接手m的所有权.

(6):

新建的unique_lock对象,尝试调用m的try_lock_for(rel_time),锁住m。具体表现可以参考try_lokc_for. rel_time是一段时间.

(7):

unique_lock对象尝试调用m的try_lock_until(abs_time)锁住m.

(8):

unique_lock可以被移动构造.

 // unique_lock constructor example
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::lock, std::unique_lock
                          // std::adopt_lock, std::defer_lock
std::mutex foo,bar;
void task_a () {
  std::lock (foo,bar);         // simultaneous lock (prevents deadlock)
  std::unique_lock<std::mutex> lck1 (foo,std::adopt_lock);
  std::unique_lock<std::mutex> lck2 (bar,std::adopt_lock);
  std::cout << "task a\n";
  // (unlocked automatically on destruction of lck1 and lck2)
}
void task_b () {
  std::unique_lock<std::mutex> lck1, lck2;
  lck1 = std::unique_lock<std::mutex>(bar,std::defer_lock); //调用unique_lock的移动构造函数. 
  lck2 = std::unique_lock<std::mutex>(foo,std::defer_lock);
  std::lock (lck1,lck2);       // simultaneous lock (prevents deadlock) //这里即使调用了std::lock也不会导致锁死(deadlock). 
  std::cout << "task b\n";
  // (unlocked automatically on destruction of lck1 and lck2)
}
void task_c()
{
 foo.lock();
 std::cout<<"task c\n";
 foo.unlock();
}
int main ()
{
  std::thread th1 (task_a);
  std::thread th2 (task_b);
  std::thread th3 (task_c);
  
  th1.join();
  th2.join();
  th3.join();
  
  return 0;
}

 std::unique_lock成员函数:

unique_lock& operator= (unique_lock&& x) noexcept;
unique_lock& operator= (const unique_lock&) = delete;

unique_lock支持移动赋值,但是拷贝被禁止了.

std::unique_lock的主要成员函数:

std::unique_lock::lock()

上锁操作,调用它所管理的 Mutex 对象的 lock 函数。如果在调用  Mutex 对象的 lock 函数时该 Mutex 对象已被另一线程锁住,则当前线程会被阻塞,直到它获得了锁。

该函数返回时,当前的 unique_lock 对象便拥有了它所管理的 Mutex 对象的锁。如果上锁操作失败,则抛出 system_error 异常。

 

std::unique_lock::try_lock()

尝试调用所管理的mutex对象的try_lock上锁。如果上锁失败返回false.成功返回true.

如果该unique_lock对象管理的mutex已经被当前线程上锁了,再调用该函数则会抛出(throw)system_error异常.或者对一个没有管理任何mutex对象的unique_lock调用该函数也会抛出异常.

 // unique_lock::try_lock example
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock
std::mutex mtx;           // mutex for critical section
void print_star () {
  std::unique_lock<std::mutex> lck(mtx,std::defer_lock); //注意这里的构造函数接受了一个std::defer_lock.
  //std::deder_lock 意味着:当前unique_lock虽然接手了mtx,但是并没有立即锁住mtx对象. 
  // print '*' if successfully locked, 'x' otherwise: 
  if (lck.try_lock())
    std::cout << '*';
  else                    
    std::cout << 'x';
}
int main ()
{
  std::vector<std::thread> threads;
  for (int i=0; i<500; ++i)
    threads.emplace_back(print_star);
  for (auto& x: threads) x.join();
  return 0;
}

 

你可能感兴趣的:(C++11 多线程 std::mutex篇 (四).)