二.共享数据的保护
文章目录
- 一、用互斥保护共享数据
-
- 1.std::mutex调用lock()加锁,unlock()解锁
- 2.std::lock_guard利用RAII机制保证发生异常时也能正常unlock
- 二、防范死锁
-
- 1.std::lock同时锁住多个互斥
- 2.std::adopt_lock指明互斥已被锁住,不得在构造函数内试图另行加锁
- 3.C++17实现了std::scoped_lock模板,是std::lock模板的RAII实现
- 4.防范死锁的准则
-
- 1.避免嵌套锁
- 2.一旦持锁,就须避免调用用户提供的程序接口
- 3.依从固定顺序获取锁
- 5.std::unique_lock灵活加锁
-
- 1.std::unique_lock第二个参数使用默认值
- 2.std::unique_lock第二个参数传入std::adopt_lock指明互斥已经lock过,std::unique_lock据此接收锁的归属权,不得在构造函数中试图另行加锁。
- 3.std::unique_lock第二个参数传入std::defer_lock指明互斥处于无锁状态,等以后有需要的时候再调用std::unique_lock对象的lock()加锁
- 3.在不同作用域转移互斥所有权,std::unique_lock可移动不可复制
- 6.按适当粒度加锁
- 7.在初始化过程中保护共享数据,std::call_once()
-
- 1.类的数据成员线程安全的延迟初始化
- 2.单例模式
-
- 1.new生成对象指针,使用std::call_once保证只执行一次初始化
- 2.C++11静态变量初始化,只会在某一个线程单独发生
一、用互斥保护共享数据
1.std::mutex调用lock()加锁,unlock()解锁
2.std::lock_guard利用RAII机制保证发生异常时也能正常unlock
#include <list>
#include <mutex>
#include <algorithm>
#include <iostream>
#include <thread>
std::list<int> some_list;
std::mutex some_mutex;void add_to_list(int new_value)
{std::lock_guard<std::mutex> guard(some_mutex);some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{std::lock_guard<std::mutex> guard(some_mutex);return std::find(some_list.begin(),some_list.end(),value_to_find)!= some_list.end();
}int main()
{std::thread t(add_to_list,42); list_contains(42);t.join();std::cin.get();
}
二、防范死锁
1.std::lock同时锁住多个互斥
2.std::adopt_lock指明互斥已被锁住,不得在构造函数内试图另行加锁
代码如下(示例):
#include <mutex>class some_big_object
{};void swap(some_big_object& lhs,some_big_object& rhs)
{}class X
{
private:some_big_object some_detail;mutable std::mutex m;
public:X(some_big_object const& sd):some_detail(sd){}friend void swap(X& lhs, X& rhs){if(&lhs==&rhs)return;std::lock(lhs.m,rhs.m);std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock);std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock);swap(lhs.some_detail,rhs.some_detail);}
};int main()
{}
3.C++17实现了std::scoped_lock模板,是std::lock模板的RAII实现
friend void swap(X& lhs, X& rhs){if(&lhs==&rhs)return;std::scoped_lock(lhs.m,rhs.m);swap(lhs.some_detail,rhs.some_detail);}
4.防范死锁的准则
1.避免嵌套锁
假如已经持有锁就应该避免获取第二个锁,万一有确有需求获取多个锁,应采用std::lock模板,借单独的调用动作一次获取全部锁来避免死锁。
2.一旦持锁,就须避免调用用户提供的程序接口
我们已经持有锁,再去调用用户实现的接口,恰好用户接口内部试图获取锁,就可能发生嵌套锁,不过有时这样是在所难免,所以我们又需要另一个新的准则。
3.依从固定顺序获取锁
如果多个锁是必要的,又无法通过std::lock模板在一部操作中完成,我们只能退而求其次,在每个线程内都依从固定的顺序获取这些锁。
5.std::unique_lock灵活加锁
与std::lock_guard比较,std::unique_lock即能很好利用RAII机制,又更加的灵活,可以根据需要,在std::unique_lock对象构造时对mutex对象加锁,也可以在std::unique_lock构造时使mutex处于无锁状态,之后调用std::unique_lock对象的lock()函数择机加锁,也可以接管已经加过锁的mutex,且允许在std::unique_lock对象销毁前调用std::unique_lock的成员函数unlock()解锁。
1.std::unique_lock第二个参数使用默认值
std::mutex m;
void f()
{std::unique_lock<std::mutex> lk(m);//构造时加锁//.....处理共享数据的事务lk.unlock();//解锁//.....处理非共享数据的事务lk.lock();//加锁//.....处理共享数据的事务
}
2.std::unique_lock第二个参数传入std::adopt_lock指明互斥已经lock过,std::unique_lock据此接收锁的归属权,不得在构造函数中试图另行加锁。
std::mutex m;
void f()
{m.lock();//互斥已经加锁std::unique_lock<std::mutex> lk(m,std::adopt_lock);//指明互斥已经加过锁//.....lk.unlock();
}
3.std::unique_lock第二个参数传入std::defer_lock指明互斥处于无锁状态,等以后有需要的时候再调用std::unique_lock对象的lock()加锁
std::mutex m;
void f()
{std::unique_lock<std::mutex> lk(m,std::defer_lock);//构造时无锁状态lk.lock();//延后加锁//...lk.unlock();//....
}
3.在不同作用域转移互斥所有权,std::unique_lock可移动不可复制
准许函数锁定互斥,然后将互斥所有权转移给函数调用者,好让他在一个锁的作用下执行其它操作。
std::mutex m;
std::unique_lock<std::mutex> f()
{std::unique_lock<std::mutex> lk(m);return lk;
}
void hold()
{std::unique_lock<std::mutex> lk(f());std::cout << __FUNCTION__ << std::endl;
}
6.按适当粒度加锁
只在必要的操作上加锁,避免必要操作外加锁
7.在初始化过程中保护共享数据,std::call_once()
1.类的数据成员线程安全的延迟初始化
#include <mutex>struct connection_info
{};struct data_packet
{};struct connection_handle
{void send_data(data_packet const&){}data_packet receive_data(){return data_packet();}
};struct remote_connection_manager
{connection_handle open(connection_info const&){return connection_handle();}
} connection_manager;class X
{
private:connection_info connection_details;connection_handle connection;std::once_flag connection_init_flag;void open_connection(){connection=connection_manager.open(connection_details);}
public:X(connection_info const& connection_details_):connection_details(connection_details_){}void send_data(data_packet const& data){std::call_once(connection_init_flag,&X::open_connection,this);connection.send_data(data);}data_packet receive_data(){std::call_once(connection_init_flag,&X::open_connection,this);return connection.receive_data();}
};int main()
{}
2.单例模式
1.new生成对象指针,使用std::call_once保证只执行一次初始化
#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
#include <chrono> class X
{
private:X() {};static void init() {static huishou hs;x = new X();std::cout << "调用std::call_once的线程ID: " << std::this_thread::get_id() << std::endl;}
public:static X* getMe(){std::call_once(X::init_flag,X::init);return x;}void print(){std::lock_guard<std::mutex> lk(m);std::cout << "单例对象地址:"<< this << " 当前线程ID: " << std::this_thread::get_id() << std::endl;}private:class huishou{public:huishou() {};~huishou() {delete X::x;};};static X* x;static std::once_flag init_flag;std::mutex m;
};X* X::x = nullptr;
std::once_flag X::init_flag;void f()
{X* x = X::getMe();x->print();
}
int main()
{std::thread t1(f);std::thread t2(f);std::thread t3(f);std::thread t4(f);t1.join();t2.join();t3.join();t4.join();
}
2.C++11静态变量初始化,只会在某一个线程单独发生
#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
#include <chrono> class X
{
private:X() {};
public:static X& getMe(){static X x;return x;}void print(){std::lock_guard<std::mutex> lk(m);std::cout << "单例对象地址:"<< this << " 当前线程ID: " << std::this_thread::get_id() << std::endl;}
private:std::mutex m;
};void f()
{X& x = X::getMe();x.print();
}
int main()
{std::thread t1(f);std::thread t2(f);std::thread t3(f);std::thread t4(f);t1.join();t2.join();t3.join();t4.join();
}