From 7a2202bef984008fd0af8033566022fcf114ab72 Mon Sep 17 00:00:00 2001 From: Xiaojian Zheng Date: Thu, 26 Oct 2023 00:39:12 +0800 Subject: [PATCH] add lock free programing --- ...n-introduction-to-lock-free-programming.md | 55 ++++++++++++++++++ docs/posts/images/lock-free- programing.png | Bin 0 -> 14479 bytes docs/posts/java- virtual-thread.md | 1 + 3 files changed, 56 insertions(+) create mode 100644 docs/posts/an-introduction-to-lock-free-programming.md create mode 100644 docs/posts/images/lock-free- programing.png diff --git a/docs/posts/an-introduction-to-lock-free-programming.md b/docs/posts/an-introduction-to-lock-free-programming.md new file mode 100644 index 0000000..61a2c60 --- /dev/null +++ b/docs/posts/an-introduction-to-lock-free-programming.md @@ -0,0 +1,55 @@ +--- +category: + - 并发编程 + - 无锁编程 +tag: + - 无锁并发 + - 译文 +date: 2023-10-26 +star: true +--- + +# 无锁编程简介 + +无锁编程是一项挑战,不仅因为任务本身的复杂性,还因为要深入理解这个主题是非常困难的。 + +我第一次介绍无锁(lock-free,又称为lockless)编程是Bruce Dawson优秀而全面的白皮书《[Lockless Programming Considerations](http://msdn.microsoft.com/en-us/library/windows/desktop/ee418650(v=vs.85).aspx)》。和很多人一样,我也有机会将Bruce的建议付诸实践,在Xbox 360等平台上开发和调试无锁代码。 + +从那时起,已经写了很多好的材料,从抽象理论和正确性证明到实际示例和硬件细节。我将在脚注中列出参考书目。有时,一个源中的信息可能看起来与其他源正交:例如,一些材料假定[顺序一致性](http://en.wikipedia.org/wiki/Sequential_consistency),从而避免了通常困扰无锁C/C++代码的内存排序问题。新的[C++11 atomic library standard](http://en.cppreference.com/w/cpp/atomic)给工作带来了另一个难题,挑战了我们许多人表达无锁算法的方式。 + +在这篇文章中,我想重新介绍无锁编程,首先定义它,然后将大部分信息提炼成几个关键概念。我将用流程图展示这些概念是如何相互关联的,然后我们将深入到细节中去。至少,任何深入研究无锁编程的程序员都应该已经了解如何使用互斥锁和其他高级同步对象,如信号量(semaphores)和事件(events),用其编写正确的多线程代码。 + +## 什么是无锁编程 + +人们通常将无锁编程描述为不使用互斥锁(mutex)的编程,互斥锁又称为[lock](http://preshing.com/20111118/locks-arent-slow-lock-contention-is)。这是事实,但这是其中一部分。基于学术文献的普遍接受的定义更广泛一些。从本质上讲,无锁是一个用于描述某些代码的属性,而不需要过多地说明这些代码实际上是如何编写的。 + +基本上,如果程序的某些部分满足以下条件,那么该部分可以被正确地认为是无锁的。相反,如果代码的给定部分不满足这些条件,那么该部分就不是无锁的。 + +![Lock free programing.png](images/lock-free-%20programing.png) + +从这个意义上说,无锁编程中的锁并不是指互斥锁(mutex),而是指以某种方式“锁定”整个应用程序的可能性,无论是死锁、活锁,甚至是由于你的死敌所做的假想线程调度决策。最后一点听起来很滑稽,但却是关键所在。共享互斥锁是不可能的,因为只要一个线程获得了互斥,你的死对头就再也不会调度该线程了。当然,真正的操作系统不是这样工作的——我们只是在定义术语。 + +这个示例没有使用互斥锁(mutex),但它仍然不是无锁的。开始时,`X = 0`。作为对读者的练习,考虑如何以一种不让两个线程退出循环的方式调度两个线程。 + +```c +while (X == 0) +{ + X = 1 - X; +} +``` + +没有人期望大型应用程序完全没有锁。通常,我们从整个代码库中识别出一组特定的无锁操作。例如,在无锁队列中,可能有一些无锁操作,如`push`、`pop`,可能还有`isEmpty`等等。 + +Herlihy & Shavit,[The Art of Multiprocessor Programming](http://www.amazon.com/gp/product/0123973376/ref=as_li_ss_tl?ie=UTF8&tag=preshonprogr-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=0123973376)的作者,倾向于用类方法(class method)来表达这样的操作,并给出了无锁的简洁定义(见150页):“在无限次执行中,无限次地有一些方法调用结束”。换句话说,只要程序能够继续调用那些无锁操作,完成调用的数量就会不断增加。从算法上看,在这些操作期间系统不可能锁定。 + +无锁编程的一个重要结果是,如果挂起单个线程,它永远不会阻止同一个整体内的其他线程执行自己的无锁操作。这暗示了在编写中断处理程序和实时系统时无锁编程的价值,在这些系统中,某些任务必须在特定的时间限制内完成,而不管程序的其余部分处于什么状态。 + +最后的精度:设计为阻塞的操作不会使算法失效。队列为空时,队列的`pop`操作可能会故意阻塞。其余的代码仍可视为无锁。 + +## 无锁编程技术 + +未完待续... + +## Reference + +1. 翻译自:[An Introduction to Lock-Free Programming](https://preshing.com/20120612/an-introduction-to-lock-free-programming/) \ No newline at end of file diff --git a/docs/posts/images/lock-free- programing.png b/docs/posts/images/lock-free- programing.png new file mode 100644 index 0000000000000000000000000000000000000000..89b2a137db7d33f000c5e7784f160e4845295edb GIT binary patch literal 14479 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�tP z>pbV8X<5%>xN|z9Xbm|raOGdb|F2c@|Fr}EUoXdW9gBaz*Z&XaW1yLpQJThMagX$& P;kk$Rb?=qmeG2