Multithreading threadsafe是什么意思?

Multithreading threadsafe是什么意思?,multithreading,thread-safety,definition,Multithreading,Thread Safety,Definition,最近,我试图从UI线程以外的线程访问文本框,但引发了一个异常。它说代码不是线程安全的,因此我最终从MSDN帮助编写了一个委托示例并调用它 但即便如此,我还是不太明白为什么需要所有额外的代码 更新: 如果我检查,我会遇到严重问题吗 Controls.CheckForIllegalCrossThread..blah =true 有一篇关于线程安全的文章 你必须跳过一则广告-抱歉这样定义它: 在计算机编程中,线程安全描述了一种程序部分或例程,它可以从多个编程线程调用,而线程之间不需要进行不必要的交互

最近,我试图从UI线程以外的线程访问文本框,但引发了一个异常。它说代码不是线程安全的,因此我最终从MSDN帮助编写了一个委托示例并调用它

但即便如此,我还是不太明白为什么需要所有额外的代码

更新: 如果我检查,我会遇到严重问题吗

Controls.CheckForIllegalCrossThread..blah =true
有一篇关于线程安全的文章

你必须跳过一则广告-抱歉这样定义它:

在计算机编程中,线程安全描述了一种程序部分或例程,它可以从多个编程线程调用,而线程之间不需要进行不必要的交互

线程是程序的执行路径。单线程程序只有一个线程,因此不会出现此问题。实际上,所有GUI程序都有多个执行路径,因此线程-至少有两个,一个用于处理GUI显示和处理用户输入,另一个用于实际执行程序操作

通过将任何长时间运行的进程卸载到任何非UI线程,这样在程序运行时UI仍能响应。这些线程可以创建一次并在程序的生命周期内存在,也可以在需要时创建,完成后销毁

由于这些线程通常需要执行常见操作(磁盘i/o、将结果输出到屏幕等),因此代码的这些部分需要以这样一种方式编写,即它们可以处理来自多个线程的调用,通常是同时调用。这将涉及以下事项:

处理数据副本 在关键代码周围添加锁 以适当的模式打开文件-因此,如果正在读取,也不要打开文件进行写入。 处理由于资源被其他线程/进程锁定而无法访问资源的问题。
您显然是在WinForms环境中工作。WinForms控件具有线程关联性,这意味着创建它们的线程是唯一可用于访问和更新它们的线程。这就是为什么您会在MSDN和其他地方找到示例,演示如何将调用打包到主线程上


通常的WinForms实践是使用一个专用于所有UI工作的线程。

用最简单的术语来说,线程安全意味着从多个线程访问它是安全的。当您在一个程序中使用多个线程,并且每个线程都试图访问内存中的公共数据结构或位置时,可能会发生一些不好的事情。因此,您需要添加一些额外的代码来防止这些不好的事情。例如,如果两个人同时编写同一文档,则要保存的第二个人将覆盖第一个人的工作。为了使其线程安全,您必须强制人员2等待人员1完成任务,然后才允许人员2编辑文档

有一篇很好的博客文章,题目是关于维基百科中线程安全的定义

从链接中提取的3个重要内容:

“如果一段代码在运行期间功能正常,则它是线程安全的 多线程同时执行。”

“特别是,它必须满足多个线程的需要 访问相同的共享数据,…”

“…以及仅由一个用户访问共享数据的需要 在任何给定的时间线程。”


绝对值得一读

如果模块保证在多线程和并发使用时能够保持不变量,那么它就是线程安全的

这里,模块可以是数据结构、类、对象、方法/过程或函数。基本上是限定范围的代码和相关数据

这种保证可能仅限于某些环境,例如特定的CPU体系结构,但必须适用于这些环境。如果没有明确的环境界限,那么通常认为它适用于所有可以编译和执行代码的环境

线程不安全的模块可能在多线程和并发使用的情况下正常工作,但这通常更多地取决于运气和巧合,而不是精心设计。即使某些模块在下没有中断,但在移动到其他环境时可能会中断

多线程错误通常很难调试。其中一些只是偶尔发生,而另一些则表现得非常积极——这也可能是特定于环境的。它们可能表现为微妙的错误结果或死锁。它们可能以不可预测的方式破坏数据结构,并导致代码的其他远程部分出现其他看似不可能的错误。它可能是非常特定于应用程序的,因此很难给出一个一般性的描述。

我发现,当一个方法具有并依赖于一个副作用(如全局变量)时,它通常被认为是不安全的线程

例如,我看到 n将浮点数格式化为字符串的代码,如果其中两个在不同的线程中运行,小数分隔符的全局值可以永久更改为“.”

//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

简单地说,线程安全意味着一个方法或类实例可以由多个线程同时使用,而不会出现任何问题

考虑以下方法:

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添加到线程A的tmp tmp was 0,然后将该值保存在myInt中。myInt又是1

因此,在本例中,方法AddOne被调用了两次,但由于该方法不是以线程安全的方式实现的,myInt的值不是预期的2,而是1,因为第二个线程在第一个线程完成更新之前读取了变量myInt


在非平凡的情况下,创建线程安全方法非常困难。还有很多技巧。在Java中,可以将一个方法标记为已同步,这意味着在给定时间只有一个线程可以执行该方法。其他线程排队等待。这使得一个方法是线程安全的,但是如果一个方法中有很多工作要做,那么这就浪费了很多空间。另一种技术是通过创建锁或信号量“只将方法的一小部分标记为已同步”,并锁定这一小部分通常称为临界部分。甚至有一些方法被实现为无锁线程安全的,这意味着它们是以这样一种方式构建的,即多个线程可以同时通过它们而不会引起任何问题,当一个方法只执行一个原子调用时可能会出现这种情况。原子调用是不能被中断的调用,一次只能由一个线程完成。

您可以从《Java并发实践》一书中获得更多解释:

如果一个类在从多个线程访问时行为正确,则该类是线程安全的,而与运行时环境对这些线程执行的调度或交错无关,并且调用代码没有额外的同步或其他协调

线程安全:线程安全程序保护其数据不受内存一致性错误的影响。在高度多线程的程序中,线程安全程序不会对同一对象上多个线程的多个读/写操作产生任何副作用。不同的线程可以共享和修改对象数据,而不会出现一致性错误

您可以通过使用高级并发API实现线程安全。本文档提供了实现线程安全的良好编程结构

支持简化许多并发应用程序的锁定习惯用法

定义用于启动和管理线程的高级API。java.util.concurrent提供的执行器实现提供了适用于大规模应用程序的线程池管理

使管理大型数据集合变得更容易,并且可以大大减少同步的需要

具有最小化同步并有助于避免内存一致性错误的功能

在JDK中,7提供了从多个线程高效生成伪随机数的功能


有关其他编程构造,请参阅和包

在现实世界中,外行的例子是

假设你有一个互联网和手机银行的银行账户,而你的账户只有10美元。 您使用手机银行将余额转账到另一个帐户,同时使用同一个银行帐户进行网上购物。 如果此银行帐户不是线程安全的,则该银行允许您同时执行两项交易,然后该银行将破产


线程安全意味着如果同时有多个线程尝试访问对象,对象的状态不会改变。

要了解线程安全,请阅读以下内容:

4.3.1。示例:使用委派的车辆跟踪器

作为一个更重要的委托示例,让我们构造一个版本的车辆跟踪器,将其委托给线程安全类。我们将位置存储在一个映射中,因此我们从一个线程安全的映射实现ConcurrentHashMap开始。我们还使用不可变点类而不是可变点来存储位置,如清单4.6所示

清单4.6。DelegatingVehicleTracker使用的不可变点类

点是线程安全的,因为它是不可变的。不可变的值可以自由共享和发布,因此我们在返回它们时不再需要复制位置

清单4.7中的DelegatingVehicleTracker不使用任何显式同步;所有访问权限 状态由ConcurrentHashMap管理,映射的所有键和值都是不可变的

清单4.7。将线程安全性委托给ConcurrentHashMap

VisualComponent使用CopyOnWriteArrayList存储每个侦听器列表;这是一个线程安全列表实现,特别适合于管理侦听器列表,请参见第5.2.3节。每个列表都是线程安全的,并且由于不存在将一个列表的状态耦合到另一个列表的状态的约束,VisualComponent可以将其线程安全职责委托给底层的mouseListeners和keyListeners对象

4.3.3。当委派失败时

大多数复合类不像VisualComponent那么简单:它们具有与其组件状态变量相关的不变量。清单4.10中的NumberRange使用两个原子整数来管理其状态,但施加了一个额外的约束,即第一个数小于或等于第二个数

清单4.10。无法充分保护其不变量的数字范围类。不要这样做

NumberRange不是线程安全的;它不保留约束上下的不变量。setLower和setUpper方法试图尊重此不变量,但效果很差。setLower和setUpper都是先检查后执行的序列,但它们没有使用足够的锁定使其原子化。如果数字范围为0、10,则一个线程调用setLower5,而另一个线程调用setUpper4,在某些不吉利的计时情况下,这两个线程都将通过setter中的检查,并且两个修改都将应用。结果是该范围现在保持5,4-无效状态。因此,虽然底层原子整数是线程安全的,但复合类不是。由于底层状态变量lower和upper不是独立的,NumberRange不能简单地将线程安全性委托给它的线程安全状态变量

NumberRange可以通过使用锁来保持其不变量,比如用一个普通的锁来保护上下两层,从而实现线程安全。它还必须避免发布lower和upper,以防止客户端破坏其不变量

如果一个类有复合动作,如NumberRange所做的那样,那么单独的委托也不是线程安全的合适方法。在这些情况下,类必须提供自己的锁定,以确保复合操作是原子的,除非整个复合操作也可以委托给底层状态变量

如果一个类由多个独立的线程安全状态变量组成,并且没有任何具有无效状态转换的操作,那么它可以将线程安全性委托给底层状态变量


通常,线程安全意味着使用该术语的人认为它意味着什么,至少对该人来说是这样。因此,它不是一个非常有用的语言构造-在讨论线程代码的行为时,您需要非常、非常具体。重复?:@dave抱歉,我尝试过搜索,但放弃了…无论如何,谢谢..一个不存在此问题的代码称为同步。对吧?对。强制各个线程等待访问共享资源可以通过同步来完成。从Gregory接受的答案来看,他说“如果一段代码在多个线程同时执行的过程中正确运行,那么它就是线程安全的。”而你说的是让它成为线程安全的,你必须强迫第一个人等待;他是不是说同步是可以接受的,而你是说不能?你能解释一下吗?都一样。我只是建议使用一种简单的机制作为代码线程安全的示例。尽管运行同一代码的多个线程不应该相互干扰,但不管使用何种机制。那么这是否只适用于使用全局变量和静态变量的代码?以您的人员编辑文档为例,我认为阻止person 2在另一个文档上运行文档编写代码是没有意义的。如果方法AddOne被调用了两次,请避免使用仅链接的答案,因为它在将来任何时候都可能变差。更新的链接:
 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}
  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }
public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}
public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}