前言

中描述的是 serilization 模式的数据结构,其锁的粒度很粗,虽然说是多线程安全的,但是无法进行并发(concurrency)访问,从而导致同一时刻只能有一个线程在操作数据结构。

如果想实现并发访问,一种是使用细粒度锁的数据结构,另一种就是无锁的数据结构设计。其实细粒度锁属于 “伪并发”,它在是否能真正设计成并发需要视应用场景而定。

虽然都有锁,粗粒度锁就是串行化访问,细粒度锁通过对具体应用场景的分析可以做到一定程度上的 “伪并发”。

 

注:以下代码均摘自 《C++ Concurrency In Action》

 

局部加锁的队列:

#pragma once

#include <queue>
#include <memory>


template <typename T>
class threadsafe_queue
{
private:
struct node {
std::shared_ptr<T> data;

//为什么要用unique_ptr,如果想做细粒度的锁控制,那么unique_ptr是必须的,细粒度控制意味着局部加锁,且各个动作之前应当是互不干扰的
//比如pop只操作队列头部,push只操作队列尾部,这样的话就可以保证生产者线程(调用push的线程)和消费者线程(调用pop的线程)可以完全并发
//进行。但是多个生产者之间还是需要同步,多个生产者之间也需要互相同步,且同步使用的就是局部锁。
//unique_ptr可以保证指针不会被导出,这样的话外部就无法对指针额外加锁和销毁指针。
//局部锁是并发访问的理论基础。
//并发访问的核心思想就是通过为固有/常见的数据结构添加一些辅助节点,以实现生产者和消费者的访问隔离,在此基础上,如果有多个生产者和多个
//消费者,则只提供生产者同步锁和消费者同步锁即可。
std::unique_ptr<node> next;
};

std::mutex head_m;
std::mutex tail_m;
std::unique_ptr<node> head;
node* tail;

node* get_tail()
{
std::lock_guard<std::mutex> lock_tail(tail_m);
return tail;
}

std::unique_ptr<node> pop_head()
{
std::lock_guard<std::mutex> lock_head(head_m);

if (head.get() == get_tail()) {
return nullptr;
}

std::unique_ptr<node> old_head = std::move(head);
head = std::move(old_head->next);
return old_head;
}

public:
threadsafe_queue()
:head(new node)
, tail(head.get())
{}

threadsafe_queue(const threadsafe_queue& other) = delete;
threadsafe_queue& operator=(const threadsafe_queue& other) = delete;

std::shared_ptr<T> try_pop()
{
std::unique_ptr<node> old_head = pop_head();
return old_head ? old_head->data : std::shared_ptr<T>();
}

void push(T new_value)
{
std::shared_ptr<T> new_data(std::make_shared<T>(std::move(new_value)));
std::unique_ptr<node> p(new node);
node* const new_tail = p.get();
std::lock_guard<std::mutex> tail_lock(tail_m);
tail->data = new_data;
tail->next = std::move(p);
tail = new_tail;
}
};