最近我尝试从一个线程(非UI线程)访问文本框,结果抛出了异常。它说什么关于“代码不是线程安全的”,所以我最终编写了一个委托(从MSDN示例中获取)并调用它来代替。
但即便如此,我还是不太明白为什么需要所有这些额外的代码。
更新: 如果我进行检查,会遇到任何严重问题吗?
Controls.CheckForIllegalCrossThread..blah =true
最近我尝试从一个线程(非UI线程)访问文本框,结果抛出了异常。它说什么关于“代码不是线程安全的”,所以我最终编写了一个委托(从MSDN示例中获取)并调用它来代替。
但即便如此,我还是不太明白为什么需要所有这些额外的代码。
更新: 如果我进行检查,会遇到任何严重问题吗?
Controls.CheckForIllegalCrossThread..blah =true
绝对值得一读!“如果在多线程同时执行时代码正确地运行,则该代码是线程安全的。”
“特别地,它必须满足多个线程访问同一共享数据的需求,…”
“…以及同一时间只能由一个线程访问共享数据的需求。”
在最简单的术语中,线程安全意味着可以安全地从多个线程访问它。当您在程序中使用多个线程并且它们每个都尝试访问共同的数据结构或内存位置时,可能会发生一些不好的事情。因此,您需要添加一些额外的代码来防止这些不良后果。例如,如果两个人同时写同一份文档,第二个人保存将覆盖第一个人的工作。为了使其线程安全,您必须强制第二个人等待第一个人完成任务,然后才允许第二个人编辑该文档。
维基百科有一篇关于线程安全的文章。
这个定义页面(你需要跳过一个广告-抱歉)给出了如下定义:
在计算机编程中,线程安全描述了程序部分或例程可以从多个编程线程中调用,而不会发生线程之间的意外交互。
线程是程序的执行路径。单线程程序只有一个线程,因此这个问题不会出现。几乎所有GUI程序都有多个执行路径和线程——至少有两个,一个用于处理GUI的显示和处理用户输入,另一个用于实际执行程序操作。
这样做是为了使界面在程序工作时仍然响应,通过将任何长时间运行的进程卸载到任何非UI线程中。这些线程可能一次创建并存在于程序的整个生命周期中,也可能在需要时创建并在完成后销毁。
由于这些线程经常需要执行常见操作——磁盘I / O、向屏幕输出结果等——因此代码的这些部分需要以这样的方式编写,以便它们可以处理从多个线程同时调用它们的情况。这将涉及以下内容:
简单地说,线程安全指的是一个方法或类实例可以同时被多个线程使用而不会发生任何问题。
考虑下面的方法:
private int myInt = 0;
public int AddOne()
{
int tmp = myInt;
tmp = tmp + 1;
myInt = tmp;
return tmp;
}
现在线程A和线程B都想执行AddOne()
方法。但是A先开始并将myInt(0)
的值读入到tmp
中。然后由于某些原因,调度程序决定停止线程A并延迟执行线程B。现在线程B也将myInt
的值(仍为0)读入其自己的变量tmp
中。线程B完成整个方法,因此最终myInt = 1
,并返回1。现在又轮到线程A了。线程A继续执行。将1加到tmp
中(对于线程A,tmp
为0)。然后将这个值保存在myInt
中。myInt
再次为1。
因此,在这种情况下,AddOne()
方法被调用两次,但由于该方法没有以线程安全的方式实现,因此myInt
的值不是预期的2,而是1,因为第二个线程在第一个线程完成更新之前读取了变量myInt
的值。
在非平凡情况下创建线程安全的方法非常困难。有很多技术可供选择。在Java中,可以将方法标记为synchronized
,这意味着在给定时间只有一个线程可以执行该方法,其他线程等待。这使得方法线程安全,但如果方法中有很多工作要做,则会浪费很多空间。另一种技术是通过创建锁或信号量,并锁定此小部分(通常称为关键部分),将“仅将方法的一小部分标记为同步”。甚至有一些方法是实现为无锁线程安全的,这意味着它们构建成这样,多个线程可以同时通过它们而不会导致问题,例如当方法只执行一个原子调用时。原子调用是不能被打断且只能由一个线程同时完成的调用。
让我们举一个现实世界的例子来解释给外行人听。
假设你有一个银行账户,账户里只有100美元。 你向你兄弟的账户转账50美元,与此同时,你的配偶正在使用同一个银行账户购物,花费了80美元。 如果这个银行账户不是线程安全的,那么银行就犯了一个很大的错误,让你和你的配偶同时进行了两笔交易,然后银行将破产!
银行账户是“共享状态”,而你和你的配偶是尝试对共享状态进行写操作的“两个不同的线程”。
线程安全意味着多个线程无法同时访问对象的状态,只有一个线程会首先执行写/读操作,然后下一个线程才能按顺序访问。
所以要么你,要么你的配偶会成功地进行第一笔交易,但不能同时进行,银行账户是线程安全的。
如果一个模块可以在多线程和并发使用的情况下保持其不变性,则该模块是线程安全的。
在这里,模块可以是数据结构、类、对象、方法/过程或函数。基本上是有作用域的代码段和相关的数据。
该保证可能仅限于某些环境,例如特定的CPU架构,但必须对这些环境保持。如果没有明确界定环境,则通常认为它适用于可以编译和执行代码的所有环境。
线程不安全的模块可能在多线程和并发使用下正常运行,但这往往更多地依赖于运气和巧合,而不是仔细的设计。即使某个模块在您的环境下不会出错,将其移动到其他环境可能会导致错误。
多线程错误通常很难调试。其中一些错误只会偶尔发生,而其他错误则表现得非常明显 - 这也可能是特定于环境的。它们可能以微妙错误的结果、死锁的形式出现。它们可能以不可预测的方式混乱数据结构,并导致其他看似不可能的错误出现在代码的其他远程部分。它可能非常应用程序特定,因此很难进行一般描述。
原子变量 具有最小化同步和避免内存一致性错误的特性。
ThreadLocalRandom(在JDK 7中)可为多个线程提供有效的伪随机数生成。
还可以参考java.util.concurrent和java.util.concurrent.atomic包以获取其他编程结构。
我认为http://en.wikipedia.org/wiki/Reentrancy_%28computing%29的概念通常被视为不安全的线程,这是指一个方法具有并依赖于副作用,例如全局变量。
例如,我曾经看到过一些代码将浮点数格式化为字符串,如果在不同的线程中运行两个这样的代码,则decimalSeparator的全局值可能会永久更改为“。”
//built in global set to locale specific value (here a comma)
decimalSeparator = ','
function FormatDot(value : real):
//save the current decimal character
temp = decimalSeparator
//set the global value to be
decimalSeparator = '.'
//format() uses decimalSeparator behind the scenes
result = format(value)
//Put the original value back
decimalSeparator = temp