lockfree的本质是乐观锁。也就是说,它假设多数情况下,别人不会改变。一个通用的lockfree算法可描述如下:
lockfree_modify(DataT* data)
{
for (;;)
{
Save old state of data to a local variable;
do modify;
lock {
if (current state == old state)
commit modify & return;
}
}
}
可以看出,lockfree也是锁,只是将锁限制在一个最小的范围内(通常是一个原子操作)。由于仍然有锁,lockfree在多核下并不会比普通的锁高明多少,它也不能随cpu个数增加而获得呈线性scale的性能提升。
不过,lockfree有个最大的好处,就是不可能有死锁。因为对上面算法分析你可以知道,在某个线程modify失败,总意味着有另一个人成功了,整个程序总在一步步前进,而不是出现相互等待而产生饥饿的状况。
我们以stack为例,展示下lockfree的stack是怎样的:
class stack
{
public:
struct Node
{
Node* prev;
};
private:
Node* m_head;
public:
stack()
: m_head(NULL)
{
}
void push(Node* val)
{
for (;;)
{
Node* head = m_head;
val->prev = head;
if (InterlockedCompareExchangePointer((PVOID*)&m_head, val, head) == head)
return;
}
}
Node* pop()
{
for (;;)
{
Node* head = m_head;
if (head == NULL)
return NULL;
if (InterlockedCompareExchangePointer((PVOID*)&m_head, head->prev, head) == head)
return head;
}
}
};
嗯,看起来挺不错,代码还算优雅。。。不过遗憾的是,严谨的说其实上面的lockfree算法是有问题的。问题在哪里?在pop算法上。我们看lock范围内的语义:
if (InterlockedCompareExchangePointer((PVOID*)&m_head, head->prev, head) == head)
return head;
这句话用伪代码描述是:
lock {
if (m_head == head) {
m_head = head->prev;
return head;
}
}
问题在于,m_head指针的值没有发生变化,和整个stack没有发生变化,这两者等价吗?
不等价。完全可以发生一种情况就是,另一个线程执行了这样一段代码:
v1 = pop(); // v1是m_head
v2 = pop();
push(v1);
此时,m_head没有变化,但是m_head->prev不再是v2,而是v2->prev了。
那么怎么办?在说解决方案前,我们先再聊下上例中的stack::push。既然stack::pop有问题,那么stack::push呢?我们说,push算法的并没有什么问题。尽管同样的,m_head指针的值没有发生变化,并不意味着整个stack没有发生变化。但是对于push来说,它只关心m_head,而不关心其他东西,所以stack是否发生变化对其并无影响。
那么,应该如何解决pop算法遇到的问题?一个比较通用的方法,就是版本号。lockfree算法中,有一个术语叫tagged_ptr,其实本质上就是一个打了版本号的pointer:
struct tagged_ptr
{
void* p;
size_t tag;
};
每次要对p进行修改,都++tag。这样,判断是不是modify,只需要判断tag是否变化即可。
分享到:
相关推荐
在IT行业中,"LockFree"是一种并发编程技术,它的核心思想是避免在多线程环境下使用锁来同步对共享资源的访问。LockFree技术在C# .NET框架中也有广泛的应用,可以显著提高多线程环境下的程序性能,降低死锁和竞态...
Lock-Free算法减少了锁导致的阻塞和上下文切换,提高了并发性能。 2. **环形链表**:数据结构中的环形链表用于构建环形队列,首尾相连,形成一个循环,简化了插入和删除操作。 3. **并发环形队列**:允许多个线程...
通过上述分析可以看出,Lock Free技术不仅可以应用于具体的算法设计,还可以用于改进现有的多线程编程模式。在实际开发过程中,合理运用Lock Free技术,结合流水线等其他高级并发控制机制,可以大大提升系统的性能和...
总结来说,LockFreeList是一种基于无锁算法实现的列表数据结构,它利用了Java的原子操作来实现线程安全的插入和删除。无锁编程虽然复杂,但在高并发场景下能提供显著的性能优势。理解并掌握无锁数据结构的设计与实现...
在"lockfree-shm-ipc"中,开发者可能采用了CAS(Compare and Swap)或FAS(Fetch and Swap)等无锁算法来实现数据的读写操作。 在压缩包中,"lsi.make"和"Makefile"是构建脚本,用于编译和链接项目。"lockfree-shm-...
### 锁自由数据结构(Lock-Free Data Structures) 在多线程编程领域,锁自由数据结构是一种重要的技术,它改变了传统上依赖锁来实现线程安全的方式。本文将深入探讨锁自由数据结构的基本概念、原理以及它们如何在...
安全内存回收 (SMR) 算法: 迈克尔的危险指针 用户空间 RCU 数据结构 - 针对不同 SMR 模式的大量侵入式和非侵入式容器算法 侵入式和非侵入式堆栈 侵入式和非侵入式队列:Michael & Scott 无锁和基于读/写锁, Moir...
无锁编程(Lock-free Programming)是一种并发控制技术,它通过避免使用锁来实现线程间的同步,从而提高多核处理器系统中的性能。在标题提到的"Lock free 论文集合"中,我们可以期待找到一系列关于无锁数据结构设计...
这个压缩包“LockFree论文集合,若干无锁数据结构实现的经典论文,500多页.zip”包含了一系列关于无锁数据结构实现的论文,对于深入理解并发编程的高级概念和技术具有重要价值。 无锁编程的核心思想是通过原子操作...
无锁队列(Lock-Free Queue)是一种在多线程编程中用于提高并发性能的数据结构。在传统的线程安全队列中,通常会使用锁来保护数据的同步,但这种做法可能会导致线程间的竞争条件,进而产生性能瓶颈。无锁队列通过...
总的来说,`lockfree-lib`库为C程序员提供了一种实现高效并发的工具,但使用时需要充分理解无锁算法的原理和潜在问题,以确保在实际应用中达到预期的效果。在具体使用这个库之前,建议仔细阅读文档,理解其提供的...
锁自由并行垃圾回收(Lock-free Parallel Garbage Collection)是一种先进的垃圾回收机制,旨在提高多处理器系统中的内存管理效率。传统的垃圾回收算法通常依赖于锁定机制来确保数据的一致性,这在多线程环境中可能...
无锁队列与环形缓冲区(Lock-free Queue and Ring Buffer)是计算机科学中的关键概念,尤其是在并发编程和多线程环境下。它们被设计用于在高并发场景下提高数据结构的性能,避免了传统锁机制所带来的性能瓶颈。下面...
本文介绍了一种新的动态内存无锁FIFO队列算法,该算法相较于目前最有效且实用的Michael和Scott的无锁FIFO队列(以下简称MS队列)表现更佳。 #### 引言 FIFO队列是支持先进先出语义的并发数据结构,支持enqueue...
上述技术可用于实现完全无锁的基于数组的队列: lockfree queue primitivestypedef struct _queue_t *queue_t;queue_t queue_create(size_t);void *queue_dequeue(queue_t);int queue_enqueue(queue_t, void *);...
- Wait-free算法通常更复杂,因为它需要考虑到所有可能的并发执行路径,以确保每个线程都能独立完成其操作。 3. **可等待FIFO**: - 在某些情况下,lock-free和wait-free并不总是最佳选择,因为它们可能增加代码...
rs-lockfree Concurrently R/W Shared Object是高性能并发程序中最频繁的操作之一。 有多种方法可以实现它,例如R/W Lock , Reference Counting , Hazard Pointers , Epoch Based Reclamation , Quiescent ...
本资源集合"awesome-lockfree"专注于这一领域的知识和工具,旨在帮助开发者理解和实践这类技术。 无锁编程是指在多线程环境下,通过避免使用传统的锁机制来实现共享数据的并发访问。它通过原子操作(Atomic ...