0%

《C++ 并发编程实战》读书笔记(1)

《C++ 并发编程实战》读书笔记(1)

在交换操作中使用std::lock()std::lock_guard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class some_big_object;

class X {
private:
some_big_object some_detail;
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); ///< 交换数据
}
};

避免死锁的提示

  1. 避免嵌套锁。
    如果你已经持有一个锁,就别再获取其他锁, 原因很简单因为每个线程仅持有一个锁。如果需要获取多个锁,就使用std::lock()这样单个动作来执行。

  2. 在持有锁时,避免调用用户提供的代码。
    因为代码是程序员写的,你不知道它会做什么。如果用户提供的代码也在获取一个锁的话,可能导致死锁。

  3. 以固定的顺序获取锁
    如果你绝对需要获取两个或更多的锁,并且不能以std::lock的单个操作取得,次优的做法是在每个线程中以相同的顺序获取它们。
    见下面例子定义的层级锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class hierarchical_mutex {
public:
explicit hierarchical_mutex(const unsigned long value)
: hierarchy_value_(value), previous_hierarchy_value_(0) {}

/// @name lock
/// @brief 用来锁定自己的函数,
/// 锁定顺序依照本线程中小的数值先锁定,到锁定大的数值
/// 如果顺序反过来则会抛出异常
///
/// @author Lijiancong, pipinstall@163.com
/// @date 2020-02-17 13:22:50
/// @warning 线程安全
void lock() {
check_for_hierarchy_violation();
internal_mutex_.lock();
update_hierarchy_value();
}

void unlock() {
this_thread_hierarchy_value_ = previous_hierarchy_value_;
internal_mutex_.unlock();
}

bool try_lock() {
check_for_hierarchy_violation();
if (!internal_mutex_.try_lock()) return false;
update_hierarchy_value();
return true;
}

private:
std::mutex internal_mutex_;

unsigned long const hierarchy_value_;
unsigned long previous_hierarchy_value_;
/// thread_local 变量会在每个线程都有一个实例
inline static thread_local unsigned long this_thread_hierarchy_value_ = 0;

void check_for_hierarchy_violation() {
if (this_thread_hierarchy_value_ >= hierarchy_value_) {
throw std::logic_error("mutex hierarchy violated");
}
}

void update_hierarchy_value() {
previous_hierarchy_value_ = this_thread_hierarchy_value_;
this_thread_hierarchy_value_ = hierarchy_value_;
}
};

在交换操作中使用std::lock()std::unique_lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class some_big_object;

class X {
private:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd) : some_detail(sd) {}

firend void swap(X& lhs, X& rhs) {
if(&lhs == & rhs) return;
std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock); ///< 获取并延迟(defer)锁定
std::unique_lock<std::mutex> lock_b(rhs.m, std::defer_lock);
std::lock(lock_a, lock_b); ///< 锁定两个锁
swap(lhs.some_detail, rhs.some_detail);
}
};

使用std::call_once的线程安全的类成员延迟初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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(conection_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();
}
};

c++11中,初始化被定义在只发生在一个线程上,并且其他线程不可以继续直到初始化完成,所以竞争条件仅仅在于哪个线程会执行初始化,而不会有更多别的问题。对于需要单一全局实例的场合,这可以用作std::call_once的替代品。

1
2
3
4
5
class my_class;
my_class& get_my_class_instance() {
static my_class instance;
return instance;
};

使用std::condition_variable 等待数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
template<typename T>
class threadsafe_queue{
private:
mutable std::mutex mut;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue() = default;
threadsafe_queue(threadsafe_queue const& other) {
std::lock_guard<std::mutex> lk(other.mut);
data_queue = other.data_queue;
}

void push(T new_value) {
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}

void wait_and_pop(T& value) {
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk,[this]{return !data_queue.empty();});
value = data_queue.front();
data_queue.pop();
}

std::shared_ptr<T> wait_and_pop() {
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]{return !data_queue.empty();});
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}

bool empty() const {
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};