C++11多線程——lock詳解

C++11提供了兩種管理鎖的類

  • std::lock_guard:與mutex RAII相關,方便線程對互斥量上鎖
  • std::unique_lock:   與mutex RAII相關,方便線程對互斥量上鎖,相比std::lock_guard提供了更好的上鎖和解鎖控制

一 lock_guard詳解

  • lock_guard是一個模板類:template<classMutex>class lock_guard;
  • lock_guard對象通常用來管理某個鎖(Lock)對象,與Mutex RALL相關,方便線程對互斥量上鎖,在其聲明週期內,它管理的鎖一直保持上鎖狀態;在其聲明週期結束之後,它鎖管理的鎖會被自動釋放(即用構造函數對鎖對象上鎖,析構函數對鎖對象解鎖)

  • 模板參數Mutex代表幾種基本的BasicLockable類型分別爲:std::mutex, std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex以及 std::unique_lock, (BasicLockable類型只需滿足兩種操作,lock和unlock。Lockable類型在BasicLockable的基礎上增加了try_lock操作。TimedLockable類型在Lockable的基礎上又增加了try_lock_for和try_lock_until操作。)

  • 注意:lock_guard對象並不負責管理Mutex對象的生命週期,它只是簡化了mutex的上鎖和解鎖操作,再其生命週期內,它鎖管理的鎖對象會一直保持上鎖狀態;聲明週期結束之後,它鎖管理的鎖對象會被自動解鎖。其最大的優點是安全易用(在出現異常時依舊可以正確解鎖,一定程度上避免了死鎖)

  •    lock_guard構造函數如下所示

  • locking-constructor (a)                   exlicit lock_guard(mutex_type& m);

    adopting-constructor (b)               lock_guard(mutex_type&m,adopt_lock_ttag);

    copy(deleted) -constructor (c)     lock_guard(const lock_guard&) = delete;

     

    a locking-constructor

             lock_guard對象管理Mutex對象m,並在構造時對m上鎖

    b adopting-constructor初始化

             lock_guard對象管理Mutex對象m,與locking初始化不同的是,Mutex對象以被當前線程鎖住。(mutex對象用adopting::lock_guard管理,最終在調用lock_guard析構函數時,m鎖對象會被自動解鎖)

    c copy-constructor

             lock_guard的拷貝構造與移動構造均被禁用

  • locking-constructor examples

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

std::mutex mtx;

void printEven(int x)
{
	if (0 == x % 2)
	{
		std::cout << x << " is even\n";
	}
	else
	{
		throw (std::logic_error("not even\n"));
	}
}

void printThreadID(int id)
{
	try
	{
		std::lock_guard<std::mutex>lck(mtx);
		printEven(id);
	}
	catch (std::logic_error&e)
	{
		//std::cout << e.what() << std::endl;
		std::cout << "[exception caught]\n";
	}
}

int main(int argc, _TCHAR* argv[])
{
	std::thread threads[10];

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

	for (auto &th : threads)
	{
		th.join();
	}
	return 0;
}


    在voidprintThreadID(int id)中,首先捕捉voidprintEven(int x)中拋出的異常,在try塊內,首先對mtx鎖對象構造lock_guard對象lck(此語句之後,mtx鎖對象由lck管理),即在try塊作用域內(也就是lck對象生命週期內),mtx鎖對象被上鎖,在lck生命週期結束時mtx鎖對象自動解鎖(在拋出異常時,依舊可正確解鎖)。
  • adopting-constructor example

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

std::mutex mtx;

void printThreadID(int id)
{
	mtx.lock();
	std::lock_guard<std::mutex>lck(mtx, std::adopt_lock);
	std::cout << "thread # " << id << '\n';
}

void testAdoptingConstructor()
{
	std::thread threads[10];

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

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

int main(int argc, _TCHAR* argv[])
{
	testAdoptingConstructor();
	return 0;
}


在void printThreadID(int id)中,首先對mtx上鎖,之後調用lock_guard的adopting-constructor來構造lck,用lck管理mtx鎖對象。(注意,std::adopt_lock表明當前線程已獲得鎖)

二 std::unique_lock

  • std::unique_lock簡介

       std::unique_lock與std::lock_guard一樣用來管理鎖對象(在拋出異常之前上鎖的對象使用unique_lock管理的鎖對象也可正常解鎖,可一定程度上避免死鎖),其與std::lock_guard類似,但是給程序員提供了足夠的靈活度。

    在構造時,unique_lock對相關需要一個Mutex鎖對象作爲其參數,新創建的unique_lock對象負責傳入的Mutex鎖對象的上鎖和解鎖操作

    unique_lock與lock_guard一樣,不負責管理Mutex鎖對象生命週期

  • std::unique_lock構造函數

default(a)             unique_lock()noexcept;

locking(b)             explicit unique_lock(mutex_type&m);

try_locking(c)         unique_lock(mutex_type&m,try_to_lock_t tag);

deferred(d)           unique_lock(mutex_type&m, defer_lock_t tag)noexcept;

adopting(e)           unique_lock(mutex_type&m,adopt_lock_ttag);

lookingfor(f)         template <classRep,class Period>unique_lock(mutex_type&m,const chrono::duration<Rep,Period>&rel_time);

 locking until(g)      template<class Clock,classDuration>unique_lock(mutex_type& m,const chrono::time_point<Clock,Duration>&abs_time);

 copy[delete](h)     unique_lock(const unique_lock&) = delete;

 move(i)               unique_lock(unique_lock&&x);

  • a default_constructor

新創建的unique_lock對象不管理任何Mutex鎖對象

  • b locking constructor

新創建的unique_lock對象管理鎖對象m,並調用m.lock()對m上鎖,若另外某個unique_lock管理了該Mutex鎖對象m,則當前線程會被阻塞。       

  • c try_locking constructor

      新創建的unique_lock對象管理鎖對象m,調用m.try_lock()嘗試上鎖,若上鎖失敗,並不會阻塞當前線程。 

  • d deferred constructor

新建的unique_lock對象管理Mutex鎖對象m,初始化時並不鎖住Mutex鎖對象m, m是一個沒有被當前線程鎖住的Mutex對象呢

  • e adopting constructor

新創建的unique_lock管理Mutex鎖對象m,m是已經被當前線程鎖住的Mutex對象,並且新創建的unique_lock對象擁有對鎖的所有權 

  • flocking for constructor

       新創建的unique_lock管理Mutex鎖對象m,並試圖通過m.try_lock_for(read_time)來鎖住Mutex對象一段時間 

  • g locking until constructor

新創建的unique_lock管理Mutex鎖對象m,並試圖通過m.try_lock_until(abs_time)在某個時間點之前鎖住Mutex鎖對象m 

  • h copy constructor —— deleted

       unique_lock對象不能被拷貝構造        

  • i move constructor

新創建的unique_lock對象擁有x所管理的Mutex鎖對象的所有權。而此時x對象如默認構造函數鎖創建的unique_lock對象一樣,不管理任何Mutex鎖對象

  •  總結:由b、e創建的unique_lock對象通常擁有Mutex對象的鎖,通過 a、d創建的unique_lock對象不會擁有鎖。通過 c、f、g創建的unique_lock在lock成功時獲得鎖
  • std::unique_lock constructor examples
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtxOne,mtxTwo;

void taskA()
{
	std::lock(mtxOne,mtxTwo);

	std::unique_lock<std::mutex>lck1(mtxOne, std::adopt_lock);
	std::unique_lock<std::mutex>lck2(mtxTwo, std::adopt_lock);

	std::cout << "taskA\n";
}

void taskB()
{
	std::unique_lock<std::mutex>lck1, lck2;

	lck1 = std::unique_lock<std::mutex>(mtxOne, std::defer_lock);
	lck2 = std::unique_lock<std::mutex>(mtxTwo, std::defer_lock);

	std::lock(lck1, lck2);

	std::cout << "taskB\n";
}




int main(int argc, _TCHAR* argv[])
{
	std::thread th1(taskA);
	std::thread th2(taskB);

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

	return 0;
}



  • std::unique(移動)賦值操作
  • 函數原型

move(a)             unique_lock& operator=(unique_lock&& x)noexcept

copy[deleted](b)    unique_lock& operator=(unique_lock&) = delete

  • 詳解:

   move assignment:移動賦值之後,有x所管理的鎖對象及其及其狀態被新的std::unique_lock取代。如果被賦值std::unique_lock對象之前已經獲得了其他Mutex對象的鎖,則在移動賦值之前調用unlock成員函數釋放其鎖佔用的鎖。而x如默認構造函數構造的std::unique_lock對象一樣,不在管理任何Mutex鎖對象。

  • std::unique_lock move assignment examples
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

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

void print_fifty(char c) {
	std::unique_lock<std::mutex> lck;         // default-constructed
	lck = std::unique_lock<std::mutex>(mtx);  // move-assigned
	for (int i = 0; i<50; ++i) { std::cout << c; }
	std::cout << '\n';
}

int main()
{
	std::thread th1(print_fifty, '*');
	std::thread th2(print_fifty, '$');

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

	return 0;
}
    
  • std::unique_lock member functions

  • 上鎖/解鎖:lock、try_lock、try_lock_for、try_lock_until、unlock
  • 獲取屬性:owns_lock(返回unique_lock對象是否獲得鎖,若獲得鎖則返回true,否則返回false),operator bool(與owns_lock一樣,大多用於條件判斷),mutex返回當前unique_lock對象所管理的Mutex對象的指針
  • 修改操作:移動賦值,swap(與另一個unique_lock對象交換他們所管理的Mutex鎖對象的所有權),release(釋放unique_lock管理的Mutex對象的所有權,並返回之前管理的Mutex對象的指針)
  • std::unique_lock::lock詳解:

        對std::unique_lock所管理的鎖對象上鎖,若在調用lock時其他線程以對該Mutex對象已被其他線程鎖住,當前線程被阻塞直至它獲得了鎖。改函數返回時,代表std::unique_lock對象已經擁有它所管理的Mutex對象的鎖,如果上鎖失敗,則拋出system_error異常。
// unique_lock::lock/unlock
#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock

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

void print_thread_id(int id) {
	std::unique_lock<std::mutex> lck(mtx, std::defer_lock);
	// critical section (exclusive access to std::cout signaled by locking lck):
	lck.lock();
	std::cout << "thread #" << id << '\n';
	lck.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;
}


  • std::unique_lock::try_lock

對std::unique_lock所管理的Mutex對象上鎖,如果上鎖成功則返回true,否則返回false

// 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);
	// 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;
}


  • std::unique_lock::try_lock_for

對std::unique_lock所管理的Mutex對象上鎖,如果上鎖成功則返回true,否則返回false

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

std::timed_mutex mtx;

void fireworks() {
	std::unique_lock<std::timed_mutex> lck(mtx, std::defer_lock);
	// waiting to get a lock: each thread prints "-" every 200ms:
	while (!lck.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";
}

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;
}


  • std::unique_lock::try_lock_until

對std::unique_lock所管理的Mutex對象上鎖,如果上鎖成功則返回true,否則返回false

// 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() {
	std::unique_lock<std::timed_mutex> lck(cinderella, std::defer_lock);
	if (lck.try_lock_until(midnight())) {
		std::cout << "ride back home on carriage\n";
		lck.unlock();
	}
	else
		std::cout << "carriage reverts to pumpkin\n";
}

void ball() {
	std::unique_lock<std::timed_mutex> lck(cinderella, std::defer_lock);
	lck.lock();
	std::cout << "at the ball...\n";

}

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

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

	return 0;
}



  • std::unique_lock::release

釋放std::unique_lock所管理對象的所有權,並返回指向其管理Mutex對象的指針(注意,std::unique_lock::release只釋放所有權,不解鎖)

// unique_lock::release example
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock

std::mutex mtx;
int count = 0;

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

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

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

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

	return 0;
}



  • std::unique_lock::owns_lock

返回std::unique_lock對象是否獲得了它所管理的Mutex鎖對象的鎖,若std::unique_lock已獲得Mutex對象的鎖,則返回true,否則返回false;

// unique_lock::operator= example
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::try_to_lock

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

void print_star() {
	std::unique_lock<std::mutex> lck(mtx, std::try_to_lock);
	// print '*' if successfully locked, 'x' otherwise: 
	if (lck.owns_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;
}




  • std::unique_lock::operator bool

std::unique_lock::operator bool與std::unique_lock::owns_lock功能相同。

// unique_lock::operator bool
#include <iostream>       // std::cout
#include <vector>         // std::vector
#include <thread>         // std::thread
#include <mutex>          // std::mutex, std::unique_lock, std::try_to_lock

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

void print_star() {
	std::unique_lock<std::mutex> lck(mtx, std::try_to_lock);
	// print '*' if successfully locked, 'x' otherwise: 
	if (lck)
		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;
}



  • std::unique_lock::mutex

此成員函數只是簡單的返回指向std::unique_lock管理的Mutex鎖對象的指針,這裏不多做介紹




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章