什么是“线程安全”?实际上意味着什么?从实际角度来看。

48

请谅解我的初学者问题...

我正在尝试使用ASP.NET和C#使用ghostscript将PDF转换为PNG。然而,我也读到了ghostscript不是线程安全的。因此,我的问题是:

  1. 实际上,“ghostscript不是线程安全”的含义是什么?如果我在具有许多并发用户同时访问的实时ASP.NET(aspx)Web应用程序中使用它会产生什么影响?

  2. 我还从另一个网站上阅读到,ghostscript ver. 8.63的主要功能是多线程渲染。这是否意味着我们的线程安全问题现在已经解决?现在的ghostscript是线程安全的吗?

  3. 我还在评估来自PDFTron的PDF2Image,它应该是线程安全的。但是每个CPU的许可证价格不便宜。为“线程安全”而支付额外费用是否值得?


如果你仔细阅读,它的意思是询问如何从本质上支持多线程的ASP.NET中安全地使用ghostscript。 - Greg
7个回答

40

一个精确的技术定义很难得到大家的一致认同。

非正式地说,“线程安全”简单地意味着“在从多个线程调用时表现得相当良好”。该对象在从多个线程调用时不会崩溃或产生疯狂的结果。

如果您打算进行涉及特定对象的多线程编程,则实际上需要解决的问题是“对象期望的线程模型是什么?”

有许多不同的线程模型。例如,“自由线程”模型是“可以从任何线程执行任何操作;对象将处理它。” 这是您处理的最简单模型,对于提供对象的人来说则是最困难的模型。

在另一端是“单线程”模型 - 所有对象的所有实例必须从一个线程中访问,而且这种情况永远不变。

然后还有一堆属于中间范畴的东西。 “公寓线程”模型是“您可以在两个不同的线程上创建两个实例,但无论使用哪个线程创建实例,您都必须始终使用该线程调用该实例的方法”。

“租用线程”模型是“您可以在两个不同的线程上调用一个实例,但您需要确保没有两个线程同时这样做”。

诸如此类。在尝试编写针对对象的线程代码之前,请先查找该对象所期望的线程模型。


8
@Eric Lippert:我希望你知道我非常喜欢你直接指向主要内容和简明解释的方式!=) - Will Marcouiller

27

假设我们有一个集合(Collection),但不能保证它是线程安全的:

var myDic = new Dictionary<string, string>();
在多线程环境下,这将抛出异常:
string s = null;
if (!myDic.TryGetValue("keyName", out s)) {
    s = new string('#', 10);
    myDic.Add("keyName", s);
}
作为一个线程正在尝试将KeyValuePair添加到字典myDic中,另一个线程可能会调用TryGetValue()。由于集合不能同时读取和写入,因此会发生异常。
然而,另一方面,如果您尝试这样做:
// Other threads will wait here until the variable myDic gets unlocked from the preceding thread that has locked it.
lock (myDic) {
    string s = null;
    if (!myDic.TryGetValue("keyName", out s)) {
        s = new string('#', 10);
        myDic.Add("keyName", s);
    }
} // The first thread that locked the myDic variable will now release the lock so that other threads will be able to work with the variable.

突然间,第二个线程尝试获取相同的“keyName”键值时,不必像第一个线程那样将其添加到字典中。

简而言之,线程安全意味着对象支持同时被多个线程使用,或者会适当锁定线程,使您不必担心线程安全性问题。

2. 我认为GhostScript现在不是线程安全的。它主要使用多个线程执行任务,这使其提供了更高的性能,仅此而已。

3. 根据您的预算和要求,它可能是值得的。但是,如果你围绕包装器构建,你可以只在方便的时候使用lock()函数,或者如果你不使用多线程,那么付费获得线程安全显然是不值得的。这意味着,只有当您的应用程序使用多线程时,您才不会遭受库不安全的后果。除非您真正需要多线程,否则不值得为线程安全的库付费。


谢谢,我现在会继续使用GhostScript :-) - N_R

11

我是Ghostscript开发人员,不会重复讲解关于线程安全的一般理论。
我们一直在努力使得GS成为线程安全的,以便在单个进程中使用gsapi_new_instance创建多个“实例”,但我们尚未完全满意这一点(这包括我们对此的QA测试)。
然而,图形库是线程安全的,并且多线程渲染依赖于此以允许我们并行地从显示列表中渲染带。多线程渲染经过了大量的QA测试,并被许多商业许可证使用来提高多核CPU性能。

当我们最终支持GS的多个实例时,您可以确定我们会宣布。大多数需要多个实例的应用程序使用单独的进程来生成每个实例,以便GS不需要线程安全。GS可以根据参数列表选项运行作业,或者可以将I/O管道传输给/从进程,以提供数据和收集输出。


这是一篇有趣的帖子!感谢你的建议,Ray! =) - Will Marcouiller

10

1)这意味着如果您在多个线程之间共享相同的Ghostscript对象或字段,它将会崩溃。例如:

private GhostScript someGSObject = new GhostScript();
...
// Uh oh, 2 threads using shared memory. This can crash!
thread1.Use(someGSObject);
thread2.Use(someGSObject);

2) 我不这么认为-多线程渲染意味着GhostScript在内部使用多个线程进行渲染。这并没有解决GhostScript在多个线程中使用不安全的问题。

3) 这里有一个问题吗?

要使GhostScript线程安全,请确保每次只有1个线程访问它。您可以通过锁定来实现:

lock(someObject)
{
   thread1.Use(someGSObject);
}
lock(someObject)
{
   thread2.Use(someGSObject);
}

2
请注意,锁定功能不能直接使用。前四行代码应该放在由Thread1执行的方法中,后四行代码应该放在由Thread2执行的方法中。这两个方法可以是同一个方法,但仍需要确保锁定对象的线程不是同一个线程 - 必须是要使用共享对象的线程。 - Michael Madsen
还要注意的是,在进行这些操作时,你可能不会遇到崩溃,因为第一个线程可能在第二个线程有机会执行任何操作之前就已经完成了 - 但由于线程是邪恶的™,你可以肯定它们总是以你不想要的方式运行 - 只有在你不调试时才会看到错误,并且它们不会让你重现你被告知的错误。 - Michael Madsen
@Judah:感谢您用例子清晰地解释。第三个问题是想问是否值得为“线程安全”而支付额外的费用,而不是“不安全”。我已经编辑过以更清楚地反映问题。再次感谢 :-) - N_R

3

如果您正在使用来自shell对象的ghostscript(即运行命令行以处理文件),则不会受到线程问题的困扰,因为每个运行实例都在服务器上的不同进程中运行。需要注意的是,当您有一个dll从C#中使用来处理PDF时,该代码需要进行同步,以防止两个线程同时执行相同的代码。


2
  1. "Thread safe"基本上意味着一段代码能够正确地工作,即使被多个线程访问。如果您在多线程应用程序中使用不安全的代码,可能会出现多种问题。最常见的问题是死锁。然而,还有更加阴险的问题(竞态条件),因为线程问题通常很难调试。

  2. 不是。多线程渲染只是意味着GS将能够更快地进行渲染,因为它正在使用线程进行渲染(理论上是这样,但在实践中并非总是如此)。

  3. 这主要取决于您想要使用渲染器的目的。如果您将使用多个线程访问应用程序,则需要担心其是否是线程安全的。否则,这不是一个大问题。


2
线程不是一个独立的进程——这就是整个问题所在。进程是隔离的单位,而线程不是。 - Eric Lippert
@Eric Lippert - 嗯...说得好。我可能应该说一个独立的执行单元? - JasCav

1

一般来说,这是一个模糊的术语。

线程安全可能在概念层面上,其中您正确同步了共享数据。这通常是库编写者所指的。

有时,它意味着并发在语言级别上得到了定义。即语言的内存模型支持并发。这很棘手!因为作为库编写者,您无法生成并发库,因为语言没有为使用许多必要的基元提供保证。这更关注编译器编写者而不是库用户。C#在这个意义上是线程安全的。

我知道我没有直接回答你的问题,但希望这有所帮助。


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