C++11: Mutex和Lock

Mutex全名mutual exclusion(互斥體),是一個可加鎖對象(lockable object),用來協助採起獨佔排他方式控制對資源的併發訪問.這裏的資源多是個object,也多是多個object的組合。爲了得到獨佔式的資源訪問能力,相應的線程必須鎖定(lock)mutex,這樣能夠防止其餘線程也鎖定mutex,直到第一個解鎖mutex的線程.ios

-----------------------------我是分割線-------------------------------------併發

簡單使用Mutex和Lock:async

假設咱們有 int val 咱們想在併發訪問(concurrent access)中保護該 val,下面是一個比較粗淺的作法.函數

int val;
std::mutex valMutex;

之後每次訪問 val 都必須鎖住 valMutex 以求獨佔.下面是一個很不粗糙的作法:this

//線程1:
//若是線程1先執行了那麼線程2就會block直到線程1種的valMutex解鎖.
valMutex.lock();

if(val >= 0){
 f(val);

}else{
f(-val);
}

valMutex.unlock();


//線程2:

valMutex.lock();
++val;
valMutex.unlock();

從上面咱們能夠看出來,凡是可能發生併發訪問(concurrent access)的地方都須要使用同一個std::mutex,不管是讀寫都是如此.可是上面也隨之帶來了問題,當上面的某個線程throw exception的時候,就會形成資源(好比上面例子中的 val 永遠被鎖住)。spa

C++標準庫提供了更好的解決方案:線程

int val;
std::mutex valMutex;

...

std::lock_guard<std::mutex> lg(valMutex); //注意這裏
if(val >= 0){
   f(val);

}else{

   f(-val)

}

上面的std::lock_guard保證即當前線程 異常形成生命週期結束 它所管理的Mutex對象也必定會被解鎖.可是須要注意的是,這樣的lock應該被限制在可能之最短週期內,由於std::lock_guard會阻塞其餘代碼並行的機會。所以安插大括號令unlock儘可能控制在最短的週期內.指針

int val;
std::mutex valMutex;

...

{
 
 {
    std::lock_guard<std::mutex> lg(valMutex);
    if(val >= 0){
       f(val);
    }else{
       f(-val);
    }
  } //lg在這個大括號結束的時候解鎖valMutex.
}

下面咱們來看一個完整的例子:code

#include <iostream>
#include <future>
#include <mutex>
#include <string>

std::mutex printMutex;

void print(const std::string& str)
{
	std::lock_guard<std::mutex> autoLock(printMutex);
	for (const char& cr : str) {
		std::cout.put(cr);
	}

	std::cout << std::endl;
}

int main()
{
	std::future<void> result1 = std::async(std::launch::async, print, "shihuawoaini");
	std::future<void> result2 = std::async(std::launch::async, print, "marryme");
	
	print("i want use my life to love you");

	return 0;
}

在我本身的電腦上面輸出結果是:對象

"i want use my life to love"

"shihuawoaini"

"marryme"

可是若是沒有使用std::lock_gard和std::mutex可能輸出的結果就是亂七八糟了.線程1在打印的時候線程2也在打印線程3也在打印因而乎根本沒有完整的句子.

遞歸的(Recursive) Lock:

class DatabaseAccess{
    private:
   
    std::recursive_mutex mutex;
    
 
    public:
    void insertData(...)
   {
     std::lock_guard<std::recursive_mutex> lg(mutex);
     ...
    }

   void createTableAndInsertData()
   {
     std::lock_guard<std::recursive_mutex> lg(mutex);
     insertData(...); //OK不會再鎖死了.
    }

上面的例子中std::recursive_mutex在 insertData()調用完後解鎖.

嘗試性的Lock以及帶時間性的Lock:

有時候當前線程想要得到一個lock可是若是不成功的話並不想阻塞(block)當前線程.針對這種狀況,mutex提供了成員函數try_lock(),該函數嘗試鎖住一個std::mutex對象,若是成功就返回true,若是失敗就返回false(即便失敗了也不會block當前線程).

可是須要注意的是try_lock也可能失敗是由於該函數的緣由,而並非由於當前std::mutex被其餘線程鎖住了.

std::mutex m;

while(m.try_lock() == false){
     doOtherStuff();
}

std::lock_guard<std::mutex> lg(m, std::adopt_lock);
...

爲了等待特定長的時間,咱們能夠選用(帶時間性的)std::time_mutex,除此以外還有std::recursive_time_mutex他們都容許你調用try_lock_for()try_lock_until(),用以等待一個時間段,或者等待直到某個時間點.

std::timed_mutex m;

if(m.try_lock_for(std::chrono::seconds(1))){
   std::lock_guard<std::time_mutex> lg(m, std::adopt_lock);

}else{
  doOtherThing();
}

處理多個Lock:

一般一個線程一次只鎖定一個std::mutex.可是有時候必須鎖住多個mutex。拿前面說講的各類加鎖方式來講,當面臨多個互斥量(mutex)的時候就會變的複雜且有風險;你或許取得了第一個的lock,可是卻拿不到第二個,這樣就發生deadlock.

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

std::mutex foo,bar;

void task_a () {
  foo.lock(); bar.lock(); 
  std::cout << "task a\n";
  foo.unlock();
  bar.unlock();
}

void task_b () {
  bar.lock(); foo.lock(); 
  std::cout << "task b\n";
  bar.unlock();
  foo.unlock();
}

int main ()
{
  std::thread th1 (task_a);
  std::thread th2 (task_b);

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

  return 0;
}

好比上面的代碼就有可能鎖死,由於task_a可能鎖住了一個std::mutex, task_b可能鎖住了另一個std::mutex.這樣兩個線程會就相互等待.

爲了解決上面例子中的問題C++標準庫給出了std::lock():

咱們能夠這樣:

std::mutex m1;
std::mutex m2;

...

{
  std::lock(m1, m2);
  std::lock_guard<std::mutex> lockM1(m1, std::adopt_lock);
  std::lock_guard<std::mutex> lockM2(m2, std::adopt_lock);

   ...

}

 

std::lock

template <class Mutex1, class Mutex2, class... Mutexes>
  void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);

std::lock會鎖住(lock)它收到的全部mutex,若是所接受的這些Mutex中若是有的已經被其餘線程鎖住了,那麼會解鎖已經被他鎖住的Mutex,等他其餘線程unlock,且阻塞(blocking)當前線程直到它所接收的這些Mutex所有在當前線程被上鎖或者拋出異常.若是是拋出異常會解鎖(unlock)全部已經被鎖住的Mutex

也就是說std::lock()這個函數主要是用來防止須要在一個線程內鎖住(lock)多個mutex的時候產生deadlock.

 

標準庫還提供了全局函數 std::try_lock會嘗試鎖住它收到的全部std::mutex:

std::mutex m1;
std::mutex m2;

int idx = std::try_lock(m1, m2);
if(idx < 0){
 std::lock_guard<std::mutex> lockM1(m1, std::adopt_lock);
 std::lock_guard<std::mutex> lockM2(m2, std::adopt_lock);

}else{
 std::cerr << "could not all mutexs" << id+1 << std::endl;
}

std::try_lock

template <class Mutex1, class Mutex2, class... Mutexes>
  int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);

std::try_lock嘗試鎖住它接收的全部的Mutex對象,若是所接收的全部Mutex並無被上鎖成功(lock),則會返回第一個上鎖失敗的std::mutex的索引(從0開始計,以參數傳遞的順序),並且解鎖全部的已經被當前線程經過當前std::try_lock上鎖了的Mutex.若是成功則返回-1須要注意的是該函數並不會形成當前線程阻塞(也就是說若是接受的全部Mutex中有的在其餘線程已經被block,當前線程也不會等待直到其餘線程解鎖,std::try_lock會直接返回第一個沒法上鎖的std::mutex對應的參數列表的下標)。

還有特別須要注意的是不管咱們是用std::lock仍是std::try_lock都會把這些上過鎖完成的std::mutex adopt(過繼)給std::lock_guard.

 

class std::lock_guard

接受一個已經在當前線程被上鎖(block)了的mutex對象或者接受一個沒有上鎖的對象在構造函數中並在std::lock_guard的構造函數中給他上鎖,若是該mutex對象已經被其餘線程上鎖了,那麼阻塞(block)當前線程直到得到上鎖權。

構造函數:

explicit lock_guard (mutex_type& m);
	
lock_guard (mutex_type& m, adopt_lock_t tag);
	
lock_guard (const lock_guard&) = delete;

由class std::lock_guard的構造函數咱們能夠看出來它是不能被拷貝的,也不能被移動的.若是接受一個已經被上鎖(block)的std::mutex的時候須要指出第二個參數std::adopt_lock_t.

 

class std::unique_lock

在瞭解class std::unique_lock以前咱們首先了解一下用於構造函數的tag:

1, 在沒有指定任何tag的狀況下對當前class std::unique_lock的構造函數運行時候給它所接受的mutex上鎖(block),若是當前mutex被其餘線程鎖住了那麼會阻塞當前線程.

2,try_to_lock這個tag, 調用當前接受的mutex的try_lock()函數.(不會形成當前線程的阻塞).

3, deferred_lock這個tag,只是初始化當前class std::unique_lock對象,並不鎖住它所接受的mutex參數.

4,adopt_lock這個tag,代表當前class std::unique_lock接受的mutex已經在當前線程被上鎖了,如今由當前class std::unique_lock來接手管理權.

unique_lock() noexcept;//默認構造函數無論理任何mutex.

explicit unique_lock (mutex_type& m);//管理一個未被上鎖的mutex並在構造函數裏給它上鎖.若是該mutex在其餘線程                                                                            //                                     被鎖定了那麼阻塞當前線程.

unique_lock (mutex_type& m, try_to_lock_t tag);//調用m的try_lock()函數,不會形成當前線程阻塞.

unique_lock (mutex_type& m, defer_lock_t tag) noexcept;//用m構造當前unique_lock可是不在構造函數中給m上鎖

unique_lock (mutex_type& m, adopt_lock_t tag);//m已經在當前線程被上鎖了,如今來接手m的管理權.

template <class Rep, class Period>
unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
//注意這個構造函數調用m的try_lock_for()函數,也就是說m只能是std::time_mutex, std::recursive_time_mutex.
//嘗試鎖住m在規定的時間段內.
//若是m此時沒有被任何線程鎖住那麼直接鎖住m.
//若是m此時被其餘線程鎖住了,且等待超時了,其餘線程也沒有unlock m.那麼接着運行當前線程下面的內容.
//若是m此時被其餘線程鎖住了,可是很快就unlock m了並無超過規定時間也接着運行當前線程下面的內容.

template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
//注意這個夠長函數調用m的try_lock_until()函數,也就是說m只能是std::time_mutex, std::recursive_time_mutex.
//嘗試在規定時間點以前鎖住m.
//若是m此時沒有被任何其餘線程鎖住,那麼直接鎖住m.
//若是m此時被其餘線程鎖住了,且等待超過了給定的時間點也沒有unlock m, 那麼接着運行當前線程下面的內容.
//若是m此時被其餘線程鎖住了,且很快m就被unlock了沒有超過規定的時間點也接着運行當前線程下面的內容.

unique_lock (const unique_lock&) = delete;//不能拷貝.

unique_lock (unique_lock&& x);//只支持移動

std::unique_lock::lock

void lock();

對所管理的mutex對象調用lock(),若是該mutex被其餘線程在鎖着那麼阻塞當前線程.

std::unique_lock::try_lock

bool try_lock();

調用所管理的mutex對象的try_lock().

std::unique_lock::try_lock_for

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

調用所管理的mutex對象的try_lock_for(),可是該mutex對象必須支持try_lock_for()。

std::unique_lock::try_lock_until

template <class Clock, class Duration>
  bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);

調用所管理的mutex對象的try_lock_until(),可是該對象必須支持try_lock_until().

std::unique_lock::unlock

void unlock();

解鎖所管理的mutex對象,若是當前對象被std::unique_lock管理的時候被上鎖了那麼解鎖該對象,若是在當前線程內當前std::unique_lock所管理的對象沒被上鎖,那麼throw exception.

std::unique_lock::mutex

mutex_type* mutex() const noexcept;

該函數返回一個指針所管理的成員函數的指針.須要注意的是:雖然返回了一個指向他所管理的mutex對象的指針,可是它並無解鎖(unlock)它所管理的mutex,也沒有釋放它對mutex對象的擁有權.

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

class my_mutex : public std::mutex {
private:
	mutable unsigned int id;

public:
	my_mutex(const unsigned int& ID) :id(ID) {}

	my_mutex() = default;
	virtual ~my_mutex() = default;

	unsigned int get_id()const noexcept;
};

unsigned int my_mutex::get_id()const noexcept
{
	return (this->id);
}

my_mutex mutex(101);

void print_ids(const int& id)
{
	std::unique_lock<my_mutex> u_lock(mutex);
	std::cout << "thread# " << id << " locked mutex " << u_lock.mutex()->get_id() << '\n';
}

int main()
{
	std::thread threads[10];

	for (unsigned int i = 0; i < 10; ++i) {
		threads[i] = std::thread(print_ids, i);
	}

	for (std::thread& ref_thread : threads) {
		ref_thread.join();
	}

	return 0;
}

std::unique_lock::release

mutex_type* release() noexcept;

該函數返回他所管理的mutex的指針,返回後再也不具備mutex的擁有權,若是返回前該mutex在當前線程被上鎖了,那麼返回後也處於上鎖(lock)狀態.

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

std::mutex mutex;
int count = 0;

void print_count_and_unlock(std::mutex* mutex)
{
	std::cout << "count: " << count << '\n';
	mutex->unlock();
}

void task()
{
	std::unique_lock<std::mutex> u_lock(mutex);
	++count;
	print_count_and_unlock(u_lock.release());
}

int main()
{
	std::vector<std::thread> threads;
	for (int i = 0; i < 10; ++i) {
		threads.push_back(std::thread(task));
	}

	for (std::thread& ref_thread : threads) {
		ref_thread.join();
	}

	return 0;
}
相關文章
相關標籤/搜索