1. 程式人生 > >C++11多執行緒——lock詳解

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

  • adefault_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鎖物件的指標,這裡不多做介紹