一:加锁和无锁的区别?
在说锁之前,咱们先讲一下关于锁的几种状态。
1、blocking(锁)
较弱保证系统向前移动,例如自旋锁,互斥锁等。为什么是较弱保证系统向前移动呢?因为这个加锁队列无论是什么操作,基本上都有锁来进行控制,如果当加上锁之后,但是有不明的信号将这个加锁操作中断了,那么这个锁就不会被(释放)解锁,那么这个队列就死掉了,而消费者线程就饿死了。
2、lock_free(无锁)
较强保证系统向前移动,例如:CAS原子操作,他们允许有限的循环。如果要实现无锁队列,只能基于原子操作,内存屏障等实现。
3、wait_free(无等待)
强保证系统向前移动,例如:exchange,fetch_add。这个无等待是对于消费者线程来说的,因为前面的blocking队列会阻塞消费者线程,怎么阻塞的呢?是通过互斥锁和条件变量这两个一起实现的,当队列为空的时候,条件变量会阻塞在消费者线程。但是这个wait_free是不阻塞的,因此没有条件变量。
讲完前面三种类型的队列之后,还要讲一下生产消费者队列。生产消费者队列是并发系统中最基本的组件之一,但是没有一个标准。但是根据生产消费者的数量可以划分为:
MPMC:多生产者/多消费者。
SPMC:单生产者/多消费者。
MPSC:多生产者/单消费者。
SPSC:单生产者/单消费者。
虽然MPMC什么样都可以用,但是当一生产者一消费者时,SPSC会更快于MPMC,所以对于选择什么样的队列,还要是看实际应用。
二:为什么要使用无锁队列?
大家都知道加锁肯定是需要时间的,而对于多线程环境下锁的开销会更大:线程切换、上下文切换、cache损坏。所以有时候时间会浪费在保护队列的争夺上面,而并非执行任务。当然上面也讲到了blocking队列被打断的情况(信号处理程序),因此对于这种信号处理程序以及硬实时系统(执行时间有限)的情况来说,无锁队列会更加实用。
三:无锁队列是不是比有锁队列性能高?
回答这个问题之前,我们要知道怎么去衡量性能的高低。是单位时间内插入元素的个数以及单位时间取出元素的个数吗?并不是这样的。
举个夸张的栗子:有锁的时间:2秒,无锁的时间:1秒。而处理程序的时间都一样:1秒。这样看好像无锁的效率更高,但是情况肯定是多样的,当处理任务的时间:10秒。这个时候对于加锁和无锁差的那1秒好像就不那么重要了,所以有锁队列更适合耗时任务。所以看性能的高低是通过看:单位时间内处理任务的个数来决定的。
四:看几个新队列的模块
1:msg_queue(blocking)适合多生产者多消费者场景。
(1):结构体
下面的结构体好多都有两份,为什么呢?是因为这个模型使用了两个队列,一个供生产者使用,一个供消费者使用,每个队列都要配一套。那有两个队列的话,消费者怎么得到数据呢?其实是生产者收数据,然后当消费者队列为空的时候,消费者队列尝试与生产者队列进行交换,如果此时的生产者队列也为空的话,那消费者就进行阻塞等待。只有当消费者队列为空,才会与生产者队列发生碰撞,其他情况生产者只碰撞生产者,消费者只碰撞消费者。https://xxetb.xetslk.com/s/2D96kH
struct __msgqueue
{
size_t msg_max; //队列的最大数量
size_t msg_cnt; //当前的数量
int linkoff; // linkoff Count 偏移多少个字节就是用于链接下一个节点的next指针
int nonblock; //是否阻塞
void *head1; //指向生产者
void *head2; //指向消费者
void **get_head; //拿到消费者的头部
void **put_head; //拿到生产者的头部
void **put_tail; //生产者的尾指针
pthread_mutex_t get_mutex; //消费者的锁
pthread_mutex_t put_mutex; //生产者的锁
pthread_cond_t get_cond; //消费者的条件变量
pthread_cond_t put_cond; //生产者的条件变量
};
(2):生产者
void msgqueue_put(void *msg, msgqueue_t *queue)
{
void **link = (void **)((char *)msg + queue->linkoff); //利用二级指针,我的线程池有讲解
*link = NULL;
pthread_mutex_lock(&queue->put_mutex);
while (queue->msg_cnt > queue->msg_max - 1 && !queue->nonblock)
pthread_cond_wait(&queue->put_cond, &queue->put_mutex);//比如我要100个,但是现在有101个,那么就发生阻塞
*queue->put_tail = link;
queue->put_tail = link;
queue->msg_cnt++;
pthread_mutex_unlock(&queue->put_mutex);
pthread_cond_signal(&queue->get_cond); //唤醒线程
}
(3):消费者
void *msg;
//当消费者要拿数据,将两个队列进行,交换。
pthread_mutex_lock(&queue->get_mutex);
if (*queue->get_head || __msgqueue_swap(queue) > 0)
{
msg = (char *)*queue->get_head - queue->linkoff;
*queue->get_head = *(void **)*queue->get_head;
}
else
msg = NULL;
pthread_mutex_unlock(&queue->get_mutex);
return msg;
(4):交换操作
static size_t __msgqueue_swap(msgqueue_t *queue)
{//将两个队列交换
void **get_head = queue->get_head;
size_t cnt;
queue->get_head = queue->put_head;
pthread_mutex_lock(&queue->put_mutex);
while (queue->msg_cnt == 0 && !queue->nonblock)
pthread_cond_wait(&queue->get_cond, &queue->put_mutex);
cnt = queue->msg_cnt;
if (cnt > queue->msg_max - 1)
pthread_cond_broadcast(&queue->put_cond);
queue->put_head = get_head;
queue->put_tail = get_head;
queue->msg_cnt = 0;
pthread_mutex_unlock(&queue->put_mutex);
return cnt;
}
2:locked_queue(wait_free)队列为空不会阻塞消费者线程,适合耗时任务的情况
(1):所含参数
//为什么说这个队列,当队列为空的时候,是不会阻塞消费者线程呢?,因为他这个没有条件变量,如果有条件变量,会让所有线程阻塞等待信号,
//但是这个没有条件变量,不需要等待,而是通过一个while循环,来进行判断,每次调用他的,在队列中有互斥锁,只有这一把,肯定都要去抢,但是只能一个抢到
//通过模板,这里默认使用队列,此队列
template <class T, typename StorageType = std::deque<T> >
class LockedQueue
{
//! Lock access to the queue.
std::mutex _lock;
//! Storage backing the queue.
StorageType _queue;
//! Cancellation flag.
volatile bool _canceled; //防止编译优化,避免出错
public:
(2):判断是否为空
///! Checks if we're empty or not with locks held
bool empty()
{
std::lock_guard<std::mutex> lock(_lock);
return _queue.empty();
}
(3):入队操作
//! Adds an item to the queue.
void add(const T& item)
{
lock();
_queue.push_back(item);
unlock();
}
//! Adds items back to front of the queue
template<class Iterator>
void readd(Iterator begin, Iterator end)
{
std::lock_guard<std::mutex> lock(_lock);
_queue.insert(_queue.begin(), begin, end);
}
(4):出队操作
T& peek(bool autoUnlock = false)
{
lock();
T& result = _queue.front();
if (autoUnlock) //用于检查队列是否为空
unlock();
return result;
}