您的位置:首页 > 编程语言 > C语言/C++

C++11多线程——lock详解

2015-04-17 11:37 239 查看

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锁对象的指针,这里不多做介绍
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: