线程安全(thread safe)是什么?

警告:我不是一个多线程编程的专家。事实上,甚至不能说我可以胜任这个东西。我的整个职业生涯,需要用到多线程来实现代码的次数也不多。所以请用一个怀疑的态度来看待我以下的所有观点。

有一个我经常问的问题是:“这个代码是线程安全的吗?”,要回答这个问题,我们很显然要先知道线程安全是什么。
但是在我们讨论这个问题前,我想先把另外一个问题搞清楚,还有一个我稍微问的没有那么多的问题是“Eric,为什么Michelle pfeiffer在照片里总是这么好看”,要回答这个难题,我咨询了一下维基百科:

一个上镜的对象指的是对象总能在照片里面很有吸引力或引人注目

为什么Michelle pfeiffer在照片里那么好看?因为她很上镜。显然的。
好的,我很高兴我们先把这个谜团解决了,但是不知怎么的又有另一个难题现在悬在我的手中。维基百科是这么定义线程安全(thread safe)的:

“A piece of code is thread-safe if it functions correctly during simultaneous execution by multiple threads.”
代码的一部分如果能在多线程同时执行时运行正确那么就是线程安全的

就跟上镜一样,这很明显,当我们问“这个代码是线程安全的吗?”我们实际上是在问“这份代码在特定情况下可以运行正常吗?”那么我们要如何来定义代码是不是正常呢?这个我们还没有任何定义。
维基百科写到:

“In particular, it must satisfy the need for multiple threads to access the same shared data, …”
尤其重要的是,必须要满足多线程访问共享数据的的需求

这好像是对的。这好像就是人们想要表达线程安全的意思。但是:

“…and the need for a shared piece of data to be accessed by only one thread at any given time.”
还有…被需要共享的数据同一个时间只能有一个线程来访问

到现在我们才开始讨论到写线程安全代码的技巧,而不再是讨论线程安全到底是什么。把一个数据锁住,这个同一个时间只有一个线程进行访问只是实现线程安全的其中一个技巧,而不是线程安全的定义。

我不是说这个定义是错的。相较于之前的线程安全的定义,这个定义完全不差。我实际上想说的是,这个定义很含糊,跟之前的“在某些情况下执行正确”表述没有什么不一样。因为,当我问“这个代码是线程安全的吗?”我常常要往回退一步讲“你是在讨论哪个场景下的应用?”和“到底是在哪个场景下表现出哪种结果才算是正确的行为?”

人们在谈论线程安全的时候,总会产生很多不一样的问题。比如,当我告诉你我有一个“线程安全的可变队列”,你可以把这个用在你的程序中时。你很高兴的写了一个线程来执行它然后又用另外一个线程频繁的来新增或者移除可变队列中的对象:

if (!queue.IsEmpty) Console.WriteLine(queue.Peek());

然后你的代码在peek抛出QueueEmptyException时崩溃了,到底发生了什么?我说这个是线程安全的,但是你的代码却在一个多线程使用场景中崩溃了。

当我说“这个队列是线程安全的”的时候,我的意思是,这个队列会持续不断的维护其内部的状态,无论其他的线程以什么样的顺序来单独执行。但是我没有说在一个队列顺序周期内多次操作它还会自行维护状态。简单的说,我所理解的正确行为跟你所理解的正确行为不是一回事。我只关心它会不会崩溃,但是你在乎的是队列能不能正确的返回相应的方法。

在这个例子中,你和我可能都谈到了不同的线程安全。可变数据的线程安全通常指的是要确保运行中共享数据永远都是最新的状态,即使是我们操作的过程中逻辑并不连贯,就跟上面的例子一样。不可变数据结构的线程安全通常讲的是执行过程中逻辑是一致的,尽管你要找的不可变快照是过时的数据。

现在的问题是要不要获取第一个元素取决于“过时”的数据,要设计一个在所有场景下都不允许有“过时”数据的真线程安全可变数据结构是十分困难的。如果你真的要将“peek”操作做到真正的线程安全,你需要一个新的方法:

if (!queue.Peek(out first)) Console.WriteLine(first);

这个是线程安全的吗?看起来的确好多了。但是如果执行完peek之后,另一个线程执行了出队(dequeues)操作呢? 代码不会崩溃,但是你已经完全把之前程序的行为都改变了。在之前的逻辑里,如果测试后有另一个线程对其进行了“出队”操作,那么第一个元素会是什么,要么程序崩溃,要么就将第一个元素的最新状态打印出来。现在你打印的第一个数据是过时的。那么这是正常的吗?如果我们不总是想要对最新的数据进行操作的话。

这里稍微暂停一下,实际上,之前版本的代码也有这个问题。如果你在进行peek成功操作之后有另一个线程执行了出队操作,而且是在你打印操作之前,那么你可能打印出的就是一个过时数据。

如果你是想要无论何时都打印出最新的数据呢?你实际上想要实现的线程安全操作是:

queue.DoSomethingToHead(first=>{Console.WriteLine(first);});

现在这个队列的作者和使用者都在怎么使用它上达成了一致意见,所以这才是真正的线程安全,对吗?

除非…有超级复杂的需求。如果在某个需求里触发了代码执行另外一个线程,从而导致了队列操作,进而产生了死锁。那么死锁算不算一个正确的行为,如果不是,那么这个方法是真正的安全吗?

是的。

现在你应该理解了我的观点,就像我之前所说的那样,如果不沟通好具体是哪种安全威胁或者没有经过验证的话,说要编写一个安全的代码是完全没有帮助的。同样的,如果不讨论哪种不合适的行为会利用线程安全机制,哪种行为预防不了而去说这个代码是线程安全的也是完全没有帮助的。线程安全和代码规范大同小异。你同意以某种形式去和一个对象进行沟通,如果你符合规范的话,它就会给你返回一个正确的结果。如果你这么做了,那么它就会按照规范那样表现,但到底怎么定义正确的反馈,这确实是一个潜在的难题。

是的,我意识到了维基百科上所说的可能是错的,我可以把它改过来,但是这里有两个我不去这么做的原因。首先,我已经表述过了,我不是这个领域的专家,我把它留个真正这个领域的专家来做。第二,我的观点不是要说维基百科是错的,而是它上面的陈述的真的十分模糊。

原文

https://docs.microsoft.com/en-us/archive/blogs/ericlippert/what-is-this-thing-you-call-thread-safe

说明

该英文原文是在10/19/2009发布的,作者指出了维基百科关于线程安全的定义十分模糊的问题,但是他没有修改其定义,而是将其留给了其他更加专业的人去修改。一年后,维基百科关于线程安全的定义已经有了更加细节的修正,基本符合了该作者所阐述的观点。

Thread safety is a computer programming concept applicable to multi-threaded code. Thread-safe code only manipulates shared data structures in a manner that ensures that all threads behave properly and fulfill their design specifications without unintended interaction. There are various strategies for making thread-safe data structures
线程安全是计算机编程多线程编程的一个概念。线程安全的代码只会在某种规范下操作共享数据,并确保其他线程的行为是准确的并保证他们的行为是符合其需求的同时其他线程不会对其有意料之外的操作。实现线程安全的数据结构有多种多样的方式。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值