C++11多线程之std::lock_guard和std::unique_lock

C++11多线程之std::lock_guard和std::unique_lock

在C++多线程对共享数据进行修改时,我们需要加互斥锁来保护数据被正确的读和写。C++提供了std::mutex,std::mutex在同一时间,只被一个线程拿到,而其他试图lock mutex的线程会被挂起(线程挂起的状态下CPU不会分给线程时间片,那么当前挂起的线程就会暂停运行。),直到该mutex被释放后,才有可能拿到mutex并继续执行。

  • 没有mutex的例子
#include 
#include 
static int global = 0;
void threadTask()
{
    for (int i = 0; i < 100000000; i++) {
        global++;
    }
    std::cout << global << std::endl;
}
int main()
{
    std::thread t1(threadTask);
    std::thread t2(threadTask);
    std::thread t3(threadTask);
    
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

运行结果
C++11多线程之std::lock_guard和std::unique_lock_第1张图片
这个程序有3个线程,每个线程各做1亿次自加,但是运行结果并不是3亿。

mutex的使用

  • 使用mutex的例子
#include 
#include 
static int global = 0;
std::mutex my_mutex;
void threadTask()
{
    for (int i = 0; i < 100000000; i++) {
   		my_mutex.lock();
        global++;
        my_mutex.unlock();
    }
    std::cout << global << std::endl;
}
int main()
{
    std::thread t1(threadTask);
    std::thread t2(threadTask);
    std::thread t3(threadTask);
    
    t1.join();
    t2.join();
    t3.join();
    return 0;

运行结果
C++11多线程之std::lock_guard和std::unique_lock_第2张图片

std::mutex的成员函数

  • lock():调用线程将锁住该互斥量。线程调用该函数会发生一下3种情况(1)如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一直拥有该锁。(2)如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3)如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)
  • unlock(),解锁释放对互斥量的所有权。
  • try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数会出现以下3种情况(1)如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock释放互斥量。(2)如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。(3)如果当前互斥量被当前调用线程锁住,则会产生死锁。

std::time_mutex

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

lock_guard的介绍和使用

  • 使用lock_guard可以对方便的管理对互斥量的加锁和解锁
    std::lock_gurad是C++11中定义的模板类。定义如下:
    template class lock_guard;
    lock_guard对象用于管理某个锁对象,因此与Mutex RAII相关,方便线程对互斥量上锁,在某个lock_guard对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。
    (类似智能指针管理动态分配的内存资源)。lock_guard对象并不负责管理mutex对象的声明周期,lock_guard对象只是简化了mutex对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个lock_guard对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。
    lock_guard对象的拷贝构造和移动构造均被禁用,因此lock_guard对象不可被拷贝构造和移动构造。
  • 使用lock_guard的例子
#include 
#include 
static int global = 0;
std::mutex my_mutex;
void threadTask()
{
    for (int i = 0; i < 100000000; i++) {
   		std::lock_guard<std::mutex> lock(my_mutex);
        global++;
    }
    std::cout << global << std::endl;
}
int main()
{
    std::thread t1(threadTask);
    std::thread t2(threadTask);
    std::thread t3(threadTask);
    
    t1.join();
    t2.join();
    t3.join();
    return 0;

运行结果
C++11多线程之std::lock_guard和std::unique_lock_第3张图片

unique_lock的介绍和使用

lock_guard最大的缺点是简单,没有给程序员提供足够的灵活度,因此,C++11标准中定义了另外一个与Mutex RAII相关类unique_lock,该类与lock_guard类相似,也很方便线程对互斥量上锁,但是它提供了更好的上锁和解锁控制。
unique_lock对象以独占所有权的方式管理mutex对象的上锁和解锁操作,所谓独占所有权,就是没有其他的unique_lock对象同时拥有某个mutex对象的所有权。
在构造或者移动时,unique_lock对象需要传递一个Mutex对象作为它的参数,新创建的unique_lock对象负责传入的Mutex对象的上锁和解锁操作。同样,unique_lock 对象同样也不负责管理 Mutex 对象的生命周期。std::unique_lock支持同时锁定多个mutex,这避免了多道加锁时的资源“死锁”问题,在使用std::condition_variable时需要使用std::unique_lock而不应该使用std::lock_guard。

std::unique_lock的第二个参数

  • std::adopt_lock :表示这个互斥量已经被lock了,你必须要把互斥量提前lock了,否则会报异常。std::adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(也就是已经lock成功了),通知lock_guard和unique_lock不需要 再构造函数中lock这个互斥量了。
  • -std::try_to_lock:我们会尝试用mutex的lock()去锁定这个mutex,但是如果没有锁定成功,也会立即返回,并不会阻塞在那里;使用这个try_to_lock的前提是你自己不能先lock。
  • std::defer_lock:的意思就是没有给mutex加锁,初始化了一个没有加锁的mutex.

std::unique_lock的成员函数

  • unique_lock构造函数:禁止拷贝构造,允许移动构造;

  • operator=:赋值操作符,允许移动赋值,禁止拷贝赋值;

  • operator bool:返回当前std::unique_lock对象是否获得了锁;

  • lock函数:调用所管理的mutex对象的lock函数;

  • try_lock函数:调用所管理的mutex对象的try_lock函数;

  • try_lock_for函数:调用所管理的mutex对象的try_lock_for函数

  • try_lock_until函数:调用所管理的mutex对象的try_lock_until函数;

  • unlock函数:调用所管理的mutex对象的unlock函数;

  • release 函数:返回所管理的mutex对象的指针,并释放所有权,但不改变mutex对象的状态;

  • owns_lock函数:返回当前std::unique_lock对象是否获得了锁;

  • mutex函数:返回当前std::unique_lock对象所管理的mutex对象的指针;

  • swap函数:交换两个unique_lock对象;

  • std::unique_lock的测试代码

#include "unique_lock.hpp"
#include 
#include 
#include 
#include 
#include 
 
namespace unique_lock_ {
 
//////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/unique_lock/
namespace {
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()
{
	// unique_lock::unique_lock: Constructs a unique_lock
	// foo.lock(); bar.lock(); // replaced by:
	std::unique_lock<std::mutex> lck1, lck2;
	lck1 = std::unique_lock<std::mutex>(bar, std::defer_lock);
	lck2 = std::unique_lock<std::mutex>(foo, std::defer_lock);
	std::lock(lck1, lck2);       // simultaneous lock (prevents deadlock)
	std::cout << "task b\n";
	// (unlocked automatically on destruction of lck1 and lck2)
}
}
 
int test_unique_lock_1()
{
	std::thread th1(task_a);
	std::thread th2(task_b);
 
	th1.join();
	th2.join();
 
	return 0;
}
 
/////////////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/lock/
namespace {
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):
	// unique_lock::lock: Calls member lock of the managed mutex object.
	lck.lock();
	std::cout << "thread #" << id << '\n';
	// unique_lock::unlock: Calls member unlock of the managed mutex object, and sets the owning state to false
	lck.unlock();
}
}
 
int test_unique_lock_2()
{
	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;
}
 
//////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/mutex/
namespace {
class MyMutex : public std::mutex {
	int _id;
public:
	MyMutex(int id) : _id(id) {}
	int id() { return _id; }
};
 
MyMutex mtx3(101);
 
void print_ids(int id) {
	std::unique_lock<MyMutex> lck(mtx3);
	// unique_lock::mutex: Returns a pointer to the managed mutex object
	std::cout << "thread #" << id << " locked mutex " << lck.mutex()->id() << '\n';
}
}
 
int test_unique_lock_3()
{
	std::thread threads[10];
	// spawn 10 threads:
	for (int i = 0; i<10; ++i)
		threads[i] = std::thread(print_ids, i + 1);
 
	for (auto& th : threads) th.join();
 
	return 0;
}
 
//////////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/operator=/
namespace {
std::mutex mtx4;           // mutex for critical section
 
void print_fifty(char c) {
	std::unique_lock<std::mutex> lck;         // default-constructed
	// unique_lock::operator=: Replaces the managed mutex object by the one in x, including its owning state
	lck = std::unique_lock<std::mutex>(mtx4);  // move-assigned
	for (int i = 0; i<50; ++i) { std::cout << c; }
	std::cout << '\n';
}
}
 
int test_unique_lock_4()
{
	std::thread th1(print_fifty, '*');
	std::thread th2(print_fifty, '$');
 
	th1.join();
	th2.join();
 
	return 0;
}
 
///////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/operator_bool/
namespace {
std::mutex mtx5;           // mutex for critical section
 
void print_star() {
	std::unique_lock<std::mutex> lck(mtx5, std::try_to_lock);
	// print '*' if successfully locked, 'x' otherwise:
	// unique_lock::operator bool: Return whether it owns a lock
	if (lck)
		std::cout << '*';
	else
		std::cout << 'x';
}
}
 
int test_unique_lock_5()
{
	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;
}
 
///////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/owns_lock/
namespace {
std::mutex mtx6;           // mutex for critical section
 
void print_star6() {
	std::unique_lock<std::mutex> lck(mtx6, std::try_to_lock);
	// print '*' if successfully locked, 'x' otherwise:
	// unique_lock::owns_lock: Returns whether the object owns a lock.
	if (lck.owns_lock())
		std::cout << '*';
	else
		std::cout << 'x';
}
}
 
int test_unique_lock_6()
{
	std::vector<std::thread> threads;
	for (int i = 0; i<500; ++i)
		threads.emplace_back(print_star6);
 
	for (auto& x : threads) x.join();
 
	return 0;
}
 
 
//////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/release/
namespace {
std::mutex mtx7;
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(mtx7);
	++count;
	// unique_lock::release: Returns a pointer to the managed mutex object, releasing ownership over it
	print_count_and_unlock(lck.release());
}
}
 
int test_unique_lock_7()
{
	std::vector<std::thread> threads;
	for (int i = 0; i<10; ++i)
		threads.emplace_back(task);
 
	for (auto& x : threads) x.join();
 
	return 0;
}
 
/////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/try_lock/
namespace {
std::mutex mtx8;           // mutex for critical section
 
void print_star8() {
	std::unique_lock<std::mutex> lck(mtx8, std::defer_lock);
	// print '*' if successfully locked, 'x' otherwise:
	// unique_lock::try_lock: Lock mutex if not locked
	// true if the function succeeds in locking the managed mutex object, false otherwise.
	if (lck.try_lock())
		std::cout << '*';
	else
		std::cout << 'x';
}
}
 
int test_unique_lock_8()
{
	std::vector<std::thread> threads;
	for (int i = 0; i<500; ++i)
		threads.emplace_back(print_star8);
 
	for (auto& x : threads) x.join();
 
	return 0;
}
 
/////////////////////////////////////////////////////////////
// reference: http://www.cplusplus.com/reference/mutex/unique_lock/try_lock_for/
namespace {
std::timed_mutex mtx9;
 
void fireworks() {
	std::unique_lock<std::timed_mutex> lck(mtx9, std::defer_lock);
	// waiting to get a lock: each thread prints "-" every 200ms:
	// unique_lock::try_lock_for: Try to lock mutex during time span
	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 test_unique_lock_9()
{
	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;
}
 
/////////////////////////////////////////////////////////////
// reference: http://en.cppreference.com/w/cpp/thread/unique_lock
namespace {
struct Box {
	explicit Box(int num) : num_things{ num } {}
 
	int num_things;
	std::mutex m;
};
 
void transfer(Box& from, Box& to, int num)
{
	// don't actually take the locks yet
	std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
	std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
 
	// lock both unique_locks without deadlock
	std::lock(lock1, lock2);
 
	from.num_things -= num;
	to.num_things += num;
 
	// 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors
}
}
 
int test_unique_lock_10()
{
	Box acc1(100);
	Box acc2(50);
 
	std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
	std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);
 
	t1.join();
	t2.join();
 
	return 0;
}
 
} // namespace unique_lock_

你可能感兴趣的:(线程)