“本质上是线程安全”的意思是什么?

36
我看到了这样一句话:“有些函数本质上是线程安全的,例如memcpy()

维基百科将“线程安全”定义为:

如果一段代码只以保证多个线程同时执行时对共享数据结构进行安全操作的方式来操作,那么它就是线程安全的。

好的。但是“本质上”是什么意思?它与继承有关吗?


1
@Thilo 我在这里读到的 - http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0349c/Chdfgjej.html - Vikram
60
似乎你有一个英语混淆,而不是编程混淆。单词 "inherent" 和 "inherit" 没有关联:http://dictionary.reference.com/browse/inherent - user2357112
15
有趣的是,引用的这句话极其荒谬。虽然显然memcpy不需要触及共享状态(因此在天真的情况下是线程安全的),但它以非同步的方式修改任意内存区域,这显然根本就不是线程安全的。 - Damon
5
@Damon: 所谓的“荒谬”引用是使用了Posix标准中“线程安全”的定义。在该定义下,memcpy被Posix要求具有线程安全性。尽管维基百科上的引用可能不一定符合这个定义,但如果我们非常小心地理解“共享数据结构”的含义,它也可以符合这个定义。因此,只有当你不理解“线程安全”所定义的背景时才会认为它荒谬。由于在Java中,“线程安全”的通常含义与C中的通常含义不同,所以标签不幸地引起了混淆。 - Steve Jessop
1
@user2357112:不,这不是“英语混淆”。他只是猜测了短语的含义。那个猜测是错误的,但问题是什么是“固有线程安全性”的意思。当你编写API或协议的文档时,准确性非常重要。 - Harold R. Eason
显示剩余7条评论
5个回答

65

它与继承无关。这是一个非正式的表达方式,更像是指
“某些函数本质上就是线程安全的”。例如,一个不会涉及任何共享值/状态的函数本来就是线程安全的,也就是“本质上是线程安全的”


7
+1表示“由其本质决定”(与需要特殊预防措施以使它们线程安全相对)。 - Thilo
11
确实与继承无关。字典定义甚至是“本质上 - 作为基本组成部分或特征存在;内在的。” - nos
2
有趣。我一直认为“inherent”和“inherited”是相关的。比如,“inherent” =“因为祖先/继承/属于一个群体而毫无疑问地存在的品质”。但实际上它们确实有不同的词根(“heredito” vs. “haereo”)。 - Ark-kun
1
+1指出这是一个非正式表达,没有技术上精确的含义。 - Kevin

16
在这个语境下,我理解为“没有被设计用来实现它,但仍然是线程安全的”。
与继承的概念没有直接关联,尽管这些词当然是相关的。 当然,这不是面向对象编程意义上的继承示例,而只是一个函数,从其核心性质中获得了线程安全属性。
当然,memcpy() 的固有线程安全性也并非魔法。任何没有内部状态或“共享”副作用的函数都会是线程安全的,这就是为什么函数式编程非常适合并行编程的原因,其中所有函数都应该是“纯”的并且缺乏副作用。
在实践中,很难在典型计算机上完成“真正的工作”而不产生副作用,特别是I/O非常受其副作用的定义。 因此,即使是纯函数式语言也经常有一些非函数式的角落。 更新:当然,memcpy() 也不是没有副作用的,其核心目的是操作内存,如果在线程之间共享,则肯定不安全。 假设只要目标区域是不同的,那么无论一个或多个线程并行运行memcpy()都没有问题。
与例如printf()相比,该函数在单个(对于进程)输出流上生成字符。 它必须明确实现(根据POSIX的要求)以实现线程安全,而memcpy()则不需要。

3
这些词汇有何关联?我在一本词典中看到了印欧语系中的“ghē-”一词,用于解释“遗产”,但我不知道它是否与“inherent”一词的词根相同。 - Steve Jessop
2
@SteveJessop它们在语义上根本没有关系。请参阅问题评论和peter.petrov的答案。它们可能共享一些词源,但我也不敢打赌。 - user395760
1
@delnan:没错,我也看到过那些陈述。这个答案同意编程概念与此无关。但是,如果unwind要向我展示一些关于单词语义相关的证据,并声称这些单词与拉丁语“hereditas”具有相同的词根,那么我会很高兴,所以我希望他能够成功 :-) 仍然可能有人质疑这种关系是“显而易见”的,但如果这只是你拥有的一般知识,那么对于那些知道的人来说就是显而易见的,对于不知道的人则不是。 - Steve Jessop

9

本质上是线程安全的函数,无需针对线程做出任何特定设计决策即可安全使用,它之所以是线程安全的,是因为它执行的任务本身就具有线程安全性,而不是通过重新设计来强制实现线程安全。比如我编写一个非常简单的函数:

int cube(int x)
{
    return x*x*x;
}

它本身是线程安全的,因为它没有读取或写入共享内存的方式。然而,我也可以通过特定的设计和同步使一个不安全的函数变得线程安全。比如说,我有一个类似于之前的函数:

void cubeshare()
{
    static int x;
    x = x * x * x;
    printf("%l", x);
}

这并不是线程安全的,完全有可能在每次使用时x的值会发生改变(虽然实际上这种情况很少见,因为x会被缓存,但假设我们没有进行任何优化)。
然而,我们可以像这样使它线程安全(以下是伪代码,真正的互斥锁更加复杂):
void cubesharesafe(mutex foo)
{
    static int x;
    lockmutex(foo);
    x = x * x * x;
    printf("%l", x);
    unlockmutex(foo);
}

然而,这并不是本质上线程安全的,我们通过重新设计来强制实现它。实际示例通常比这更复杂,但我希望这能够在最简单的水平上给出一个想法。如果您有任何问题,请在下方评论。


@SteveJessop 你好,有没有可能看一下这个例子是否更好?谢谢。 - Vality
@SteveJessop:一个典型的memcpy实现如果不用于写入可能被另一个线程使用的内存,也不读取可能被另一个线程写入的内存,则是非阻塞和线程安全的。使用DMA控制器执行大于某个大小的移动的实现可能比典型实现更快,但即使不同线程访问的所有内存区域完全不相交,也可能无法在没有操作系统帮助的情况下同时实现非阻塞和线程安全。 - supercat
@Vality:对我来说,原始引用可能是指memcpy在本质上是线程安全的。 - Steve Jessop

4

在使用memcpy时,只有单个线程能够将特定源的写入传输到特定目的地。:因此具有最初设计的线程安全性。

固有的意味着:在不需要“调整”基本函数以实现目标的情况下,即实现线程安全的情况下。

如果多个线程可以同时干扰同一“通道”,那么您将面临与共享数据块相关的线程安全问题。


1
当然,多个线程可以同时调用memcpy将数据写入相同的目标位置,但灾难性的结果不是memcpy的错,而是调用者做了一些愚蠢的事情。另一方面,strtok_不是_线程安全的,即使两个调用者完全执行不相关的操作。这是由于接口设计引起的,因此它本质上不是线程安全的。 - gnasher729

1

inherent的意思是“作为永久性存在于某物中”。它与继承无关。 默认情况下,或者已经有一些方法是线程安全的...为了保护或避免多任务问题... vector、hash table等是一些本质上是线程安全的示例类... 有一些函数是默认情况下是线程安全的,这并不令人困惑。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接