一、lock-free(无锁)
系统作为一个整体无论如何都向前移动(至少有一个线程向前移动) ,不能保证每个线程的前进进度(可能出现线程饿死)。
使用互斥锁不能保证系统作为一个整体无论如何都向前移动 :如果当前操作临界资源的线程出现异常了,没有释放锁,就会导致其他线程无法向前移动。如果队列为空,所有的消费者线程都会处于阻塞状态。 通常使用 compare_exchange
原语实现。 可以有循环,但类似 compare_exchange
实现的自旋锁不行。
二、wait-free(无等待)
在其他线程争夺锁、有的线程发生阻塞等情况下,每个线程都向前移动 ,每个操作在有限步骤中执行。 通常使用 exchange
、fetch_add
等原语实现,并且不包含可能被其他线程影响的循环 。
三、blocking(有锁)
整个系统可能不会取得任何进展 ,阻塞、中断或终止的线程可能无限地阻止系统范围内的向前。
四、无锁队列
使用原子变量、原子操作,但是不像互斥锁、自旋锁那样:需要对一个变量做标记、以及有一个循环不断地去检测这个标记,设置标记。 无锁队列是一种特殊类型的队列,它在多线程环境下允许数据的并发访问和修改,而无需使用传统的锁机制来实现线程同步。 什么时候使用无锁队列 ?
先用有锁队列进行实现,然后分析是否需要保证系统向前移动,需要的话用无锁队列进行优化 。队列中的任务执行时间较短,可以用无锁队列;队列中的任务执行时间较长,还是使用有锁队列 。只能使用无锁队列的情况:
信号处理程序:
信号处理程序可以在程序执行的任何时刻被调用,包括在其他信号处理程序或在持有锁的代码执行期间。 如果信号处理程序尝试获取一个已经被持有的锁,它将无法获取锁,导致程序挂起或死锁,因为持有锁的线程可能正在等待信号处理程序完成。 实时系统:执行时间有严格的上限。
五、生产者消费者队列
生产者 — 消费者队列是并发系统中最基本的组件之一。 根据允许的生产者和消费者线程的数量,可以划分为:
MPMC:多生产者 / 多消费者队列 。SPMC:单生产者 / 多消费者队列。 MPSC:多生产者 / 单消费者队列 。SPSC:单生产者 / 单消费者队列。 根据底层数据结构,可以划分为:
Array-based:基于数组。
基于数组的队列通常更快,但是它们通常不是严格无锁的。 缺点是它们需要为最坏的情况预先分配内存。 Linked-list-based:基于链表。
链表队列是动态增长的,因此不需要预先分配任何内存。 Hybrid:混合队列。
根据链表队列是否是侵入式的,可以划分为:
Intrusive:侵入式:插入的任务作为节点内存的一部分。 Non-intrusive:非侵入式:插入的任务作为链表中的一个节点。 如果需要转换已经动态分配的数据,则侵入式队列通常具有更好的性能 ,因为不需要管理额外的节点内存。
在准备 Node 的时候,会跟 cache line 进行匹配,那么这些 Node 的访问性能会更高。 根据链表队列长度的最大大小,可以划分为:
Bounded:有界。 Unbounded:无界。 无界队列很危险,通常需要一个有界队列,因为它将强制执行你认为应该发生的事情,如有界队列中溢出部分如何处理(丢弃或者覆盖最老的数据),从而避免无法控制的事情。
六、有锁队列实现
最基本的有锁队列,采用互斥锁 ,各种情况都可以使用,主要用于任务执行时间长、有耗时运算的情况。消费者线程不会被阻塞 。
# ifndef MARK_LOCKEDQUEUE_H
# define MARK_LOCKEDQUEUE_H
# include <deque>
# include <mutex>
template < class T , typename StorageType = std:: deque< T> >
class LockedQueue
{
std:: mutex _lock;
StorageType _queue;
volatile bool _canceled;
public :
LockedQueue ( )
: _canceled ( false )
{
}
virtual ~ LockedQueue ( )
{
}
void add ( const T& item)
{
lock ( ) ;
_queue. push_back ( item) ;
unlock ( ) ;
}
template < class Iterator >
void readd ( Iterator begin, Iterator end)
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
_queue. insert ( _queue. begin ( ) , begin, end) ;
}
bool next ( T& result)
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
if ( _queue. empty ( ) )
return false ;
result = _queue. front ( ) ;
_queue. pop_front ( ) ;
return true ;
}
template < class Checker >
bool next ( T& result, Checker& check)
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
if ( _queue. empty ( ) )
return false ;
result = _queue. front ( ) ;
if ( ! check. Process ( result) )
return false ;
_queue. pop_front ( ) ;
return true ;
}
T& peek ( bool autoUnlock = false )
{
lock ( ) ;
T& result = _queue. front ( ) ;
if ( autoUnlock)
unlock ( ) ;
return result;
}
void cancel ( )
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
_canceled = true ;
}
bool cancelled ( )
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
return _canceled;
}
void lock ( )
{
this -> _lock. lock ( ) ;
}
void unlock ( )
{
this -> _lock. unlock ( ) ;
}
void pop_front ( )
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
_queue. pop_front ( ) ;
}
bool empty ( )
{
std:: lock_guard< std:: mutex> lock ( _lock) ;
return _queue. empty ( ) ;
}
int size ( ) {
std:: lock_guard< std:: mutex> lock ( _lock) ;
return _queue. size ( ) ;
}
} ;
# endif
# include "LockedQueue.h"
# include <thread>
# include <iostream>
class Check1 {
public :
bool Process ( int val) {
return val % 2 == 0 ;
}
} ;
class Check2 {
public :
bool Process ( int val) {
return val % 2 != 0 ;
}
} ;
int main ( ) {
LockedQueue< int > queue;
std:: thread pd1 ( [ & ] ( ) {
queue. add ( 1 ) ;
queue. add ( 2 ) ;
queue. add ( 3 ) ;
queue. add ( 4 ) ;
} ) ;
std:: thread pd2 ( [ & ] ( ) {
queue. add ( 5 ) ;
queue. add ( 6 ) ;
queue. add ( 7 ) ;
queue. add ( 8 ) ;
} ) ;
std:: thread cs1 ( [ & ] ( ) {
std:: this_thread:: sleep_for ( std:: chrono:: microseconds ( 1000 ) ) ;
int ele;
Check1 check;
while ( ! queue. empty ( ) ) {
if ( queue. next ( ele, check) ) {
std:: cout << "cs1: " << std:: this_thread:: get_id ( ) << " : pop " << ele << std:: endl;
}
}
} ) ;
std:: thread cs2 ( [ & ] ( ) {
std:: this_thread:: sleep_for ( std:: chrono:: microseconds ( 10 ) ) ;
int ele;
Check2 check;
while ( ! queue. empty ( ) ) {
if ( queue. next ( ele, check) ) {
std:: cout << "cs2: " << std:: this_thread:: get_id ( ) << " : pop " << ele << std:: endl;
}
}
} ) ;
pd1. join ( ) ;
pd2. join ( ) ;
cs1. join ( ) ;
cs2. join ( ) ;
return 0 ;
}
适用于各种情况。消费者线程会被阻塞 。
多生产者多消费者的情况下,锁的碰撞比较大:生产者和生产者时刻发生碰撞;生产者和消费者发生碰撞;消费者和消费者发生碰撞。
# ifndef _MARK_PC_QUEUE_H
# define _MARK_PC_QUEUE_H
# include <condition_variable>
# include <mutex>
# include <queue>
# include <atomic>
# include <type_traits>
template < typename T >
class ProducerConsumerQueue
{
private :
std:: mutex _queueLock;
std:: queue< T> _queue;
std:: condition_variable _condition;
std:: atomic< bool > _shutdown;
public :
ProducerConsumerQueue < T> ( ) : _shutdown ( false ) { }
void Push ( const T& value)
{
std:: lock_guard< std:: mutex> lock ( _queueLock) ;
_queue. push ( std:: move ( value) ) ;
_condition. notify_one ( ) ;
}
bool Empty ( )
{
std:: lock_guard< std:: mutex> lock ( _queueLock) ;
return _queue. empty ( ) ;
}
size_t Size ( ) const
{
return _queue. size ( ) ;
}
bool Pop ( T& value)
{
std:: lock_guard< std:: mutex> lock ( _queueLock) ;
if ( _queue. empty ( ) || _shutdown)
return false ;
value = _queue. front ( ) ;
_queue. pop ( ) ;
return true ;
}
void WaitAndPop ( T& value)
{
std:: unique_lock< std:: mutex> lock ( _queueLock) ;
while ( _queue. empty ( ) && ! _shutdown)
_condition. wait ( lock) ;
if ( _queue. empty ( ) || _shutdown)
return ;
value = _queue. front ( ) ;
_queue. pop ( ) ;
}
void Cancel ( )
{
std:: unique_lock< std:: mutex> lock ( _queueLock) ;
while ( ! _queue. empty ( ) )
{
T& value = _queue. front ( ) ;
DeleteQueuedObject ( value) ;
_queue. pop ( ) ;
}
_shutdown = true ;
_condition. notify_all ( ) ;
}
private :
template < typename E = T>
typename std :: enable_if< std:: is_pointer< E> :: value> :: type DeleteQueuedObject ( E& obj) { delete obj; }
template < typename E = T>
typename std :: enable_if< ! std:: is_pointer< E> :: value> :: type DeleteQueuedObject ( E const & ) { }
} ;
# endif
七、无锁队列实现
# ifndef _MARK_MPSC_QUEUE_H
# define _MARK_MPSC_QUEUE_H
# include <atomic>
# include <utility>
template < typename T >
class MPSCQueueNonIntrusive
{
public :
MPSCQueueNonIntrusive ( ) : _head ( new Node ( ) ) , _tail ( _head. load ( std:: memory_order_relaxed) )
{
Node* front = _head. load ( std:: memory_order_relaxed) ;
front-> Next. store ( nullptr , std:: memory_order_relaxed) ;
}
~ MPSCQueueNonIntrusive ( )
{
T* output;
while ( Dequeue ( output) )
delete output;
Node* front = _head. load ( std:: memory_order_relaxed) ;
delete front;
}
void Enqueue ( T* input)
{
Node* node = new Node ( input) ;
Node* prevHead = _head. exchange ( node, std:: memory_order_acq_rel) ;
prevHead-> Next. store ( node, std:: memory_order_release) ;
}
bool Dequeue ( T* & result)
{
Node* tail = _tail. load ( std:: memory_order_relaxed) ;
Node* next = tail-> Next. load ( std:: memory_order_acquire) ;
if ( ! next)
return false ;
result = next-> Data;
_tail. store ( next, std:: memory_order_release) ;
delete tail;
return true ;
}
private :
struct Node
{
Node ( ) = default ;
explicit Node ( T* data) : Data ( data)
{
Next. store ( nullptr , std:: memory_order_relaxed) ;
}
T* Data;
std:: atomic< Node* > Next;
} ;
std:: atomic< Node* > _head;
std:: atomic< Node* > _tail;
MPSCQueueNonIntrusive ( MPSCQueueNonIntrusive const & ) = delete ;
MPSCQueueNonIntrusive& operator = ( MPSCQueueNonIntrusive const & ) = delete ;
} ;
template < typename T , std:: atomic< T* > T:: * IntrusiveLink>
class MPSCQueueIntrusive
{
public :
MPSCQueueIntrusive ( ) : _dummyPtr ( reinterpret_cast < T* > ( std:: addressof ( _dummy) ) ) , _head ( _dummyPtr) , _tail ( _dummyPtr)
{
std:: atomic< T* > * dummyNext = new ( & ( _dummyPtr-> * IntrusiveLink) ) std:: atomic < T* > ( ) ;
dummyNext-> store ( nullptr , std:: memory_order_relaxed) ;
}
~ MPSCQueueIntrusive ( )
{
T* output;
while ( Dequeue ( output) )
delete output;
}
void Enqueue ( T* input)
{
( input-> * IntrusiveLink) . store ( nullptr , std:: memory_order_release) ;
T* prevHead = _head. exchange ( input, std:: memory_order_acq_rel) ;
( prevHead-> * IntrusiveLink) . store ( input, std:: memory_order_release) ;
}
bool Dequeue ( T* & result)
{
T* tail = _tail. load ( std:: memory_order_relaxed) ;
T* next = ( tail-> * IntrusiveLink) . load ( std:: memory_order_acquire) ;
if ( tail == _dummyPtr)
{
if ( ! next)
return false ;
_tail. store ( next, std:: memory_order_release) ;
tail = next;
next = ( next-> * IntrusiveLink) . load ( std:: memory_order_acquire) ;
}
if ( next)
{
_tail. store ( next, std:: memory_order_release) ;
result = tail;
return true ;
}
T* head = _head. load ( std:: memory_order_acquire) ;
if ( tail != head)
return false ;
Enqueue ( _dummyPtr) ;
next = ( tail-> * IntrusiveLink) . load ( std:: memory_order_acquire) ;
if ( next)
{
_tail. store ( next, std:: memory_order_release) ;
result = tail;
return true ;
}
return false ;
}
private :
std:: aligned_storage_t< sizeof ( T) , alignof ( T) > _dummy;
T* _dummyPtr;
std:: atomic< T* > _head;
std:: atomic< T* > _tail;
MPSCQueueIntrusive ( MPSCQueueIntrusive const & ) = delete ;
MPSCQueueIntrusive& operator = ( MPSCQueueIntrusive const & ) = delete ;
} ;
template < typename T , std:: atomic< T* > T:: * IntrusiveLink = nullptr >
using MPSCQueue = std:: conditional_t< IntrusiveLink != nullptr , MPSCQueueIntrusive< T, IntrusiveLink> , MPSCQueueNonIntrusive< T>> ;
# endif