是什么使方法具有线程安全性?都有些什么样的规矩?


156

是否存在使线程安全的方法的总体规则/准则?我了解可能有上百万种一次性情况,但总的来说呢?这样简单吗?

  1. 如果方法仅访问局部变量,则它是线程安全的。

是吗 那也适用于静态方法吗?

@Cybis提供的一个答案是:

局部变量不能在线程之间共享,因为每个线程都有自己的堆栈。

静态方法也是如此吗?

如果将方法传递给引用对象,是否会破坏线程安全性?我已经做过一些研究,关于某些情况,有很多东西,但是我希望能够仅使用一些规则来定义遵循准则,以确保方法是线程安全的。

因此,我想我的最终问题是:“是否有一小段定义线程安全方法的规则?如果是,它们是什么?”

编辑
在这里已经提出了很多优点。我认为这个问题的真正答案是:“没有简单的规则可以确保线程安全。” 凉。精细。但总的来说,我认为接受的答案提供了一个很好的简短摘要。总是有例外。就这样吧。我可以忍受这一点。


59
您不能访问其他线程也没有锁访问的变量。
汉斯·帕桑

4
汉斯(Hanth)可悲的变成了伊戈尔!
马丁·詹姆斯

3
另外,“您不应访问其他线程也没有锁定地访问过的变量”-如果所读取的值有时不是最新值或实际上严重不正确,则无所谓。
马丁·詹姆斯

是埃里克(Eric)的一个不错的博客,它带您进入旋风。
RBT

Answers:


139

如果一个方法(实例或静态)仅引用该方法范围内的变量,则它是线程安全的,因为每个线程都有自己的堆栈:

在这种情况下,多个线程可以ThreadSafeMethod并发调用而不会出现问题。

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

如果该方法调用仅引用局部作用域变量的其他类方法,则也是如此:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

如果方法访问任何(对象状态)属性或字段(实例或静态),则需要使用锁以确保值不会被其他线程修改。

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

您应该意识到,传递给该方法的任何不是结构也不是不可变的参数都可能被该方法范围之外的另一个线程所突变。

为了确保适当的并发性,您需要使用锁定。

有关更多信息,请参见锁语句C#参考ReadWriterLockSlim

对于一次提供一个功能
ReadWriterLockSlim最有用,如果需要多个读取器和单个写入器,则锁定很有用。


15
在第三个示例private string someValue;中,不是static每个实例都将获得该变量的单独副本。因此,您能否解释一下这不是线程安全的吗?
Bharadwaj 2014年

29
@Bharadwaj,如果有Thing多个线程访问该类的一个实例
Trevor

111

如果方法仅访问局部变量,则它是线程安全的。是吗

绝对不是。您可以编写仅具有从单个线程访问的单个局部变量的程序,但是该变量不是线程安全的:

https://stackoverflow.com/a/8883117/88656

那也适用于静态方法吗?

绝对不。

@Cybis提供的一个答案是:“局部变量不能在线程之间共享,因为每个线程都有自己的堆栈。”

绝对不。局部变量的显着特征是,它仅在局部范围内可见,而不是在临时池中分配完全合法,可以从两个不同的线程访问相同的局部变量。您可以通过使用匿名方法,lambda,迭代器块或异步方法来实现。

静态方法也是如此吗?

绝对不。

如果将方法传递给引用对象,是否会破坏线程安全性?

也许。

我已经做过一些研究,关于某些情况,有很多东西,但是我希望能够仅使用一些规则来定义遵循准则,以确保方法是线程安全的。

您将不得不学习失望地生活。这是一个非常困难的主题。

因此,我想我的最终问题是:“是否有一小段定义线程安全方法的规则?

不。从前面示例中可以看到,空方法可以是非线程安全的。您也可能会问“是否有确保方法正确的规则的简短列表”。不,那里没有。线程安全不过是一种极其复杂的正确性。

而且,您所提问题的事实表明您对线程安全有根本的误解。线程安全是程序的全局属性,而不是局部属性。之所以如此难以正确,是因为您必须完全了解整个程序的线程行为,以确保其安全性。

再次查看我的示例:每个方法都是微不足道的。这些方法在“全局”级别上相互交互的方式使程序陷入僵局。您无法查看所有方法并将其检查为“安全”,然后期望整个程序是安全的,这不仅仅可以得出结论,因为您的房屋是由100%非空心砖制成的,所以房屋也是不空心的。房屋的空心性是整个事物的全局属性,而不是其各个部分的属性的总和。


14
核心声明: 线程安全性是程序的全局属性,而不是局部属性。
Greg D

5
@BobHorn:class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } 调用M(),然后在两个不同的线程上调用C.getter和C.setter。现在可以在两个不同的线程上读写局部变量,即使它是局部变量也是如此。再次:局部变量的定义特征是它是局部的,而不是它在线程的堆栈上
埃里克·利珀特 Eric Lippert)2012年

3
@BobHorn:我认为这与在一定程度上了解我们的工具有关,以便我们可以凭权威和知识谈论它们。例如,原始问题出卖了对什么是局部变量缺乏理解。该错误很常见,但事实是它是一个错误,应予以纠正。局部变量的定义非常精确,应相应对待。:)这不是没有得到病理病例的“人”的答案。这些是澄清,以便您可以了解实际要求。:)
Greg D

7
@EricLippert:许多类的MSDN文档都声明此类型的公共静态(在Visual Basic中为Shared)成员是线程安全的。不保证任何实例成员都是线程安全的。或有时“此类型是线程安全的”。(例如String),当线程安全成为全球关注的问题时,如何做出这些保证?
Mike Zboray 2012年

3
@mikez:好问题。这些方法要么不更改任何状态,要么在不更改任何状态的情况下安全地更改没有锁的状态,或者如果使用锁,则以确保不违反全局“锁顺序”的方式这样做。字符串是不可变的数据结构,其方法不会改变任何内容,因此可以轻松地使字符串安全。
埃里克·利珀特2012年

11

没有硬性规定。

以下是一些使.NET中的代码线程安全的规则以及为什么这些规则不是好规则:

  1. 函数及其调用的所有函数必须是纯函数(无副作用)并且使用局部变量。尽管这将使您的代码具有线程安全性,但是.NET中使用此限制可以执行的有趣的事情也很少。
  2. 在一个通用对象lock上运行的每个函数都必须在一个通用对象上。所有锁定必须以相同的顺序进行。这将使代码线程安全,但速度会非常慢,并且您最好不要使用多个线程。
  3. ...

没有使代码线程安全的规则,您唯一可以做的就是确保无论代码被执行多少次,代码都可以正常工作,每个线程可以随时中断,每个线程都在它自己的状态/位置,以及用于访问公共对象的每个函数(静态或其他)的状态/位置。


10
锁不必太慢。锁的速度非常快;一个无可争议的锁大约在十到一百纳秒的数量。当然,有争议的锁任意慢。如果因为争用锁而放慢了速度,请重新设计程序以消除争用
埃里克·利珀特

2
我想我没有使#2足够清楚。我试图说的是有一种使线程安全的方法:将锁锁定在任何公共对象的每次访问周围。并假设有多个线程,它将产生争用,而锁将使整个过程变慢。在添加锁时,不能仅仅盲目添加锁,而必须将它们放置在非常重要的战略位置。
EarlNameless 2012年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.