我怎么知道这个C#方法是否是线程安全的?

时间:2022-02-12 04:02:16

I'm working on creating a call back function for an ASP.NET cache item removal event.

我正在为ASP.NET缓存项目删除事件创建一个回调函数。

The documentation says I should call a method on an object or calls I know will exist (will be in scope), such as a static method, but it said I need to ensure the static is thread safe.

文档说我应该调用一个对象或调用我知道将存在的调用(将在范围内),例如静态方法,但它说我需要确保静态是线程安全的。

Part 1: What are some examples of things I could do to make it un-thread safe?

第1部分:我可以采取哪些措施来使其成为非线程安全的?

Part 2: Does this mean that if I have

第2部分:这是否意味着,如果我有

static int addOne(int someNumber){
    int foo = someNumber;
    return foo +1; 
}

and I call Class.addOne(5); and Class.addOne(6); simutaneously, Might I get 6 or 7 returned depending on who which invocation sets foo first? (i.e. a race condition)

我叫Class.addOne(5);和Class.addOne(6);同时,我可能会返回6或7,具体取决于谁首先调用foo? (即比赛条件)

11 个解决方案

#1


That addOne function is indeed thread safe because it doesn't access any data that could be accessed by another thread. Local variables cannot be shared among threads because each thread gets its own stack. You do have to make sure, however, that the function parameters are value types and not reference types.

addOne函数确实是线程安全的,因为它不访问任何可由另一个线程访问的数据。局部变量不能在线程之间共享,因为每个线程都有自己的堆栈。但是,您必须确保函数参数是值类型而不是引用类型。

static void MyFunction(int x) { ... } // thread safe. The int is copied onto the local stack.

static void MyFunction(Object o) { ... } // Not thread safe. Since o is a reference type, it might be shared among multiple threads. 

#2


No, addOne is thread-safe here - it only uses local variables. Here's an example which wouldn't be thread-safe:

不,addOne在这里是线程安全的 - 它只使用局部变量。这是一个不是线程安全的例子:

 class BadCounter
 {
       private static int counter;

       public static int Increment()
       {
             int temp = counter;
             temp++;
             counter = temp;
             return counter;
       }
 }

Here, two threads could both call Increment at the same time, and end up only incrementing once. (Using return ++counter; would be just as bad, by the way - the above is a more explicit version of the same thing. I expanded it so it would be more obviously wrong.)

这里,两个线程可以同时调用Increment,最后只增加一次。 (顺便说一下,使用return ++ counter;也就是那么糟糕 - 上面是同一个东西的更明确的版本。我扩展了它,所以它显然是错误的。)

The details of what is and isn't thread-safe can be quite tricky, but in general if you're not mutating any state (other than what was passed into you, anyway - bit of a grey area there) then it's usually okay.

什么是和不是线程安全的细节可能是相当棘手的,但一般来说,如果你没有改变任何状态(除了传递给你的东西,无论如何 - 那里的灰色区域)那么它通常是好的。

#3


Threading issues (which I've also been worrying about lately) arise from the use of multiple processor cores with separate caches, as well as from basic thread-swapping race conditions. If the caches for separate cores access the same memory location, they will generally have no idea about the other one and may separately track the state of that data location without it going back out to main memory (or even to a synchronized cache shared across all cores at L2 or L3, for example), for processor performance reasons. So even order-of-execution interlock tricks may not be reliable in multi-threaded environments.

线程问题(我最近也一直在担心)是由于使用带有独立缓存的多个处理器内核以及基本的线程交换竞争条件。如果单独内核的高速缓存访​​问相同的内存位置,他们通常不会知道另一个内存位置,并且可能会单独跟踪该数据位置的状态,而不会返回到主内存(甚至是所有共享的同步高速缓存)出于处理器性能原因,例如,L2或L3处的核心。因此,即使是执行顺序的联锁技巧在多线程环境中也可能不可靠。

As you may know, the main tool to correct for this is a lock, which provides a mechanism for exclusive access (between contentions for the same lock) and handles the underlying cache synchronization so that accesses to the same memory location by various lock-protected code sections will be properly serialized. You can still have race conditions between who gets the lock when and in what order, but that's usually much simpler to deal with when you can guarantee that execution of a locked section is atomic (within the context of that lock).

您可能知道,纠正此问题的主要工具是锁,它提供独占访问机制(在同一锁的争用之间)并处理底层缓存同步,以便通过各种锁保护访问相同的内存位置代码部分将被正确序列化。你可以在何时以及以什么顺序获得锁定之间存在竞争条件,但是当你可以保证锁定部分的执行是原子的时(在该锁的上下文中),这通常更容易处理。

You can get a lock on an instance of any reference type (eg. inherits from Object, not value types like int or enums, and not null), but it's very important to understand that the lock on an object has no inherent effect on accesses to that object, it only interacts with other attempts to get the lock on the same object. It is up to the class to protect access to its member variables using an appropriate locking scheme. Sometimes instances might protect multi-threaded accesses to their own members by locking on themselves (eg. lock (this) { ... } ), but usually this is not necessary because instances tend to be held by only one owner and don't need to guarantee threadsafe access to the instance.

你可以锁定任何引用类型的实例(例如,从Object继承,而不是像int或enums这样的值类型,而不是null),但是理解对象上的锁对访问没有固有影响是非常重要的。对于该对象,它仅与其他尝试相互作用以获取对同一对象的锁定。由类使用适当的锁定方案来保护对其成员变量的访问。有时实例可以通过锁定自己来保护对自己成员的多线程访问(例如,锁定(this){...}),但通常这不是必需的,因为实例往往只由一个所有者持有而不是需要保证对实例的线程安全访问。

More commonly, a class creates a private lock (eg. private readonly object m_Lock = new Object(); for separate locks within each instance to protect access to members of that instance, or private static readonly object s_Lock = new Object(); for a central lock to protect access to the class's static members). Josh has a more specific code example of using a lock. You then have to code the class to use the lock appropriately. In more complex cases you might even want to create separate locks for different groups of members, to reduce contention for different kinds of resources which aren't used together.

更常见的是,类创建私有锁(例如,私有只读对象m_Lock = new Object();用于每个实例中的单独锁以保护对该实例的成员的访问,或私有静态只读对象s_Lock = new Object(); for一个*锁,以保护对类的静态成员的访问)。 Josh有一个更具体的使用锁的代码示例。然后,您必须对类进行编码以适当地使用锁。在更复杂的情况下,您甚至可能希望为不同的成员组创建单独的锁,以减少对未使用的不同类型资源的争用。

So, to bring it back to your original question, a method which only accesses its own local variables and parameters would be thread-safe, because these exist in their own memory locations on the stack specific to the current thread, and can not be accessed elsewhere--unless you shared those parameter instances across threads before passing them.

因此,为了使它回到原始问题,只访问其自己的局部变量和参数的方法将是线程安全的,因为它们存在于特定于当前线程的堆栈上它们自己的内存位置,并且无法访问其他 - 除非您在传递它们之前在线程之间共享这些参数实例。

A non-static method which only accesses the instances own members (no static members)--and of course parameters and local variables--would not need to use locks in the context of that instance being used by a single owner (doesn't need to be thread-safe), but if instances were intended to be shared and wanted to guarantee thread-safe access, then the instance would need to protect access to its member variables with one or more locks specific to that instance (locking on the instance itself being one option)--as opposed to leaving it up to the caller to implement their own locks around it when sharing something not intended to be thread-safe shareable.

只访问实例自己的成员(没有静态成员)的非静态方法 - 当然还有参数和局部变量 - 不需要在单个所有者使用的该实例的上下文中使用锁(不是需要是线程安全的,但如果要共享实例并希望保证线程安全访问,那么实例需要使用一个或多个特定于该实例的锁来保护对其成员变量的访问(锁定在实例本身就是一个选项) - 而不是将它留给调用者在共享不希望线程安全可共享的东西时围绕它实现自己的锁。

Access to readonly members (static or non-static) which aren't ever manipulated is generally safe, but if the instance it holds is not itself thread-safe or if you need to guarantee atomicity across multiple manipulations of it, then you may need to protect all access to it with your own locking scheme as well. That's a case where it could be handy if the instance uses locking on itself, because you could simply get a lock on the instance across multiple accesses into it for atomicity, but you wouldn't need to do so for single accesses into it if it's using a lock on itself to make those accesses individually thread-safe. (If it's not your class, you'd have to know whether it locks on itself or is using a private lock which you can't access externally.)

访问那些从未被操作的只读成员(静态或非静态)通常是安全的,但是如果它所拥有的实例本身不是线程安全的,或者如果你需要保证多次操作它的原子性,那么你可能需要用你自己的锁定方案来保护对它的所有访问。如果实例使用锁定本身就可以很方便,因为你可以简单地通过多次访问来实现对实例的锁定以获得原子性,但是如果它是单独访问它就不需要这样做。在自身上使用锁来使这些访问单独是线程安全的。 (如果不是你的班级,你必须知道它是自己锁定还是使用你无法从外部访问的私人锁。)

And finally, there's access to changing static members (changed by the given method or by any others) from within an instance--and of course static methods which access those static members and could be called from anyone, anywhere, anytime--which have the biggest need to use responsible locking, without which are definitely not thread-safe and are likely to cause unpredictable bugs.

最后,可以从实例中访问更改静态成员(由给定方法或其他任何方式更改) - 当然还有访问这些静态成员的静态方法,可以随时随地从任何人调用 - 这些方法具有最大的需要使用负责任的锁定,没有它肯定不是线程安全的,并可能导致不可预测的错误。

When dealing with .NET framework classes, Microsoft documents in MSDN whether a given API call is thread-safe (eg. static methods of the provided generic collection types like List<T> are made thread-safe while instance methods may not be--but check specifically to be sure). The vast majority of the time (and unless it specifically says it's thread-safe), it's not internally thread-safe, so it's your responsibility to use it in a safe manner. And even when individual operations are implemented internally thread-safe, you still have to worry about shared and overlapping access by your code if it does anything more complex which needs to be atomic.

在处理.NET框架类时,Microsoft在MSDN中记录给定的API调用是否是线程安全的(例如,提供的通用集合类型(如List )的静态方法是线程安全的,而实例方法可能不是 - 但具体检查确定)。绝大多数时候(除非它特别说它是线程安全的),它不是内部线程安全的,所以你有责任以安全的方式使用它。即使在内部线程安全的情况下实现单个操作,如果代码执行任何需要原子的更复杂的操作,您仍然需要担心代码的共享和重叠访问。

One big caveat is iterating over a collection (eg. with foreach). Even if each access to the collection gets a stable state there's no inherent guarantee that it won't change in between those accesses (if anywhere else can get to it). When the collection is held locally there's generally no problem, but a collection which could be changed (by another thread or during your loop's execution!) could produce inconsistent results. One easy way to solve this is to use an atomic thread-safe operation (inside your protective locking scheme) to make a temporary copy of the collection (MyType[] mySnapshot = myCollection.ToArray();) and then iterate over that local snapshot copy outside the lock. In many cases this avoids the need for holding a lock the whole time, but depending on what you're doing within the iteration this may not be enough and you just have to protect against changes the whole time (or you may already have it inside a locked section guarding against access to change the collection along with other things, so it's covered).

一个重要的警告是迭代一个集合(例如,使用foreach)。即使对集合的每次访问都获得稳定状态,也没有固有的保证,它不会在这些访问之间发生变化(如果其他任何地方都可以访问它)。当集合在本地保存时通常没有问题,但是可以更改的集合(通过另一个线程或在循环执行期间!)可能会产生不一致的结果。解决此问题的一种简单方法是使用原子线程安全操作(在保护性锁定方案内)来创建集合的临时副本(MyType [] mySnapshot = myCollection.ToArray();)然后迭代该本地快照在锁外复制。在许多情况下,这避免了一直持有锁的需要,但是根据你在迭代中所做的事情,这可能是不够的,你只需要在整个时间内防止变化(或者你可能已经在里面一个锁定的部分,防止访问改变集合以及其他东西,所以它被覆盖)。

So, there's a bit of an art to thread-safe design, and knowing just where and how to get locks to protect things depends a lot on the overall design and usage of your class(es). It can be easy to get paranoid and think you have to get locks all over for everything, but really it's about finding the right layer at which to protect things.

因此,线程安全设计有一点艺术,并且知道锁定以保护事物的位置和方法在很大程度上取决于您的类的整体设计和使用。可能很容易变得偏执,并认为你必须为所有事情获得锁定,但实际上它是关于找到保护事物的正确层。

#4


Your method is fine since it is only using local variables, let's change your method a bit:

你的方法很好,因为它只使用局部变量,让我们稍微改变你的方法:

static int foo;

static int addOne(int someNumber)
{
  foo=someNumber; 
  return foo++;
}

This is not a thread safe method because we are touching static data. This would then need to be modified to be:

这不是一个线程安全的方法,因为我们正在触摸静态数据。然后需要将其修改为:

static int foo;
static object addOneLocker=new object();
static int addOne(int someNumber)
{
  int myCalc;
  lock(addOneLocker)
  {
     foo=someNumber; 
     myCalc= foo++;
  }
  return myCalc;
}

Which I think this is a silly sample I just did cause if I'm reading it correctly there is no point in foo anymore but hey it's a sample.

我认为这是一个愚蠢的样本,如果我正确地读它就会导致foo没有任何意义,但是嘿,这是一个样本。

#5


There is some research going on which allows you to detect non-thread-safe code. E.g. the project CHESS at Microsoft Research.

有一些研究允许您检测非线程安全的代码。例如。微软研究院的CHESS项目。

#6


This would only be a race condition if it were modifying some variable external to the function. Your example is not doing that.

如果它正在修改函数外部的某个变量,那么这只会是竞争条件。你的例子不是那样做的。

That's basically what you're looking out for. Thread safe means that the function either:

这基本上就是你所期待的。线程安全意味着该功能:

  1. Does not modify external data, or
  2. 不修改外部数据,或

  3. Access to external data is properly synchronized so that only one function can access it at any one time.
  4. 正确同步对外部数据的访问,以便任何时候只有一个功能可以访问它。

External data could be something held in storage (database/file), or something internal to the application (a variable, an instance of a class, etc): basically anything that is declared anywhere in the world that is outside of the function's scope.

外部数据可以是存储(数据库/文件)中的东西,也可以是应用程序内部的东西(变量,类的实例等):基本上在世界任何地方声明的任何超出函数范围的东西。

A trivial example of an un-thread safe version of your function would be this:

您的函数的非线程安全版本的一个简单示例是:

private int myVar = 0;

private void addOne(int someNumber)
{
   myVar += someNumber;
}

If you call this from two different threads without synchronization, querying the value of myVar will be different depending on whether the query happens after all calls to addOne are complete, or the query happens in between the two calls, or the query happens before either of the calls.

如果你在没有同步的情况下从两个不同的线程调用它,查询myVar的值将会有所不同,具体取决于在所有对addOne的调用完成后查询是否发生,或者查询是在两次调用之间发生,还是查询发生在两者之前电话。

#7


In the above example no.

在上面的例子中没有。

Thread safety is mainly to do with stored state. You can make the above example non thread safe by doing this:

线程安全主要与存储状态有关。您可以通过执行以下操作使上面的示例非线程安全:

static int myInt;

static int addOne(int someNumber){
myInt = someNumber;
return myInt +1; 
}

This will mean that due to context switching thread 1 might get to the call myInt = someNumber and then context switch, lets say thread 1 just set it to 5. Then imagine that thread 2 comes in and uses 6 and returns 7. Then when thread 1 wakes up again it will have 6 in myInt instead of the 5 that it was using and return 7 instead of the expected 6. :O

这意味着由于上下文切换线程1可能会调用myInt = someNumber然后上下文切换,让我们说线程1只是将它设置为5.然后想象线程2进来并使用6并返回7.然后当线程1再次唤醒它将在myInt中有6而不是它正在使用的5并且返回7而不是预期的6.:O

#8


Anywhere, thread safe means that you don't have two or more threads colliding when you are accessing a resource. Usually static varaiables --- in languages like C#, VB.NET and Java --- made your code thread unsafe.

在任何地方,线程安全意味着在访问资源时没有两个或多个线程发生冲突。通常静态变量 - 用C#,VB.NET和Java等语言 - 使你的代码线程不安全。

In Java exists the synchronized keyword. But in .NET you get the assembly option/directive:

在Java中存在synchronized关键字。但是在.NET中你得到汇编选项/指令:


class Foo
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Bar(object obj)
    {
        // do something...
    }
}

Examples of non thread safe classes should be singletons, depending on how is this pattern coded. Usually it must implement a synchronized instance creator.

非线程安全类的示例应该是单例,具体取决于此模式的编码方式。通常它必须实现同步的实例创建者。

If you don't want a synchronized method, you can try locking method, such as spin-lock.

如果您不想使用synchronized方法,可以尝试使用锁定方法,例如自旋锁定。

#9


any access to an obect that can be used simultaneously by two threads is not threadsafe.

对两个线程可以同时使用的对象的任何访问都不是线程安全的。

your example in Part 2 is clearly safe, as it uses only values passed in as arguments, but if you used an object scoped variable you might have to surround the access with appropriate lock statements

第2部分中的示例显然是安全的,因为它仅使用作为参数传入的值,但如果您使用了对象范围变量,则可能必须使用适当的锁定语句来包围访问

#10


foo is not shared between concurrent or sequential invocations, so addOne is thread-safe.

并发或顺序调用之间不共享foo,因此addOne是线程安全的。

#11


The reason that 'foo' and 'someNumber' are safe in your example is that they reside on the stack, and each thread has their own stack and so are not shared.

在您的示例中,'foo'和'someNumber'是安全的原因是它们驻留在堆栈上,并且每个线程都有自己的堆栈,因此不会共享。

As soon as data has the potential to be shared, for example, being global or sharing pointers to objects, then you could have conflicts and may need to use locks of some sort.

一旦数据有可能被共享,例如,全局或共享指向对象的指针,那么您可能会遇到冲突,并且可能需要使用某种类型的锁。

#1


That addOne function is indeed thread safe because it doesn't access any data that could be accessed by another thread. Local variables cannot be shared among threads because each thread gets its own stack. You do have to make sure, however, that the function parameters are value types and not reference types.

addOne函数确实是线程安全的,因为它不访问任何可由另一个线程访问的数据。局部变量不能在线程之间共享,因为每个线程都有自己的堆栈。但是,您必须确保函数参数是值类型而不是引用类型。

static void MyFunction(int x) { ... } // thread safe. The int is copied onto the local stack.

static void MyFunction(Object o) { ... } // Not thread safe. Since o is a reference type, it might be shared among multiple threads. 

#2


No, addOne is thread-safe here - it only uses local variables. Here's an example which wouldn't be thread-safe:

不,addOne在这里是线程安全的 - 它只使用局部变量。这是一个不是线程安全的例子:

 class BadCounter
 {
       private static int counter;

       public static int Increment()
       {
             int temp = counter;
             temp++;
             counter = temp;
             return counter;
       }
 }

Here, two threads could both call Increment at the same time, and end up only incrementing once. (Using return ++counter; would be just as bad, by the way - the above is a more explicit version of the same thing. I expanded it so it would be more obviously wrong.)

这里,两个线程可以同时调用Increment,最后只增加一次。 (顺便说一下,使用return ++ counter;也就是那么糟糕 - 上面是同一个东西的更明确的版本。我扩展了它,所以它显然是错误的。)

The details of what is and isn't thread-safe can be quite tricky, but in general if you're not mutating any state (other than what was passed into you, anyway - bit of a grey area there) then it's usually okay.

什么是和不是线程安全的细节可能是相当棘手的,但一般来说,如果你没有改变任何状态(除了传递给你的东西,无论如何 - 那里的灰色区域)那么它通常是好的。

#3


Threading issues (which I've also been worrying about lately) arise from the use of multiple processor cores with separate caches, as well as from basic thread-swapping race conditions. If the caches for separate cores access the same memory location, they will generally have no idea about the other one and may separately track the state of that data location without it going back out to main memory (or even to a synchronized cache shared across all cores at L2 or L3, for example), for processor performance reasons. So even order-of-execution interlock tricks may not be reliable in multi-threaded environments.

线程问题(我最近也一直在担心)是由于使用带有独立缓存的多个处理器内核以及基本的线程交换竞争条件。如果单独内核的高速缓存访​​问相同的内存位置,他们通常不会知道另一个内存位置,并且可能会单独跟踪该数据位置的状态,而不会返回到主内存(甚至是所有共享的同步高速缓存)出于处理器性能原因,例如,L2或L3处的核心。因此,即使是执行顺序的联锁技巧在多线程环境中也可能不可靠。

As you may know, the main tool to correct for this is a lock, which provides a mechanism for exclusive access (between contentions for the same lock) and handles the underlying cache synchronization so that accesses to the same memory location by various lock-protected code sections will be properly serialized. You can still have race conditions between who gets the lock when and in what order, but that's usually much simpler to deal with when you can guarantee that execution of a locked section is atomic (within the context of that lock).

您可能知道,纠正此问题的主要工具是锁,它提供独占访问机制(在同一锁的争用之间)并处理底层缓存同步,以便通过各种锁保护访问相同的内存位置代码部分将被正确序列化。你可以在何时以及以什么顺序获得锁定之间存在竞争条件,但是当你可以保证锁定部分的执行是原子的时(在该锁的上下文中),这通常更容易处理。

You can get a lock on an instance of any reference type (eg. inherits from Object, not value types like int or enums, and not null), but it's very important to understand that the lock on an object has no inherent effect on accesses to that object, it only interacts with other attempts to get the lock on the same object. It is up to the class to protect access to its member variables using an appropriate locking scheme. Sometimes instances might protect multi-threaded accesses to their own members by locking on themselves (eg. lock (this) { ... } ), but usually this is not necessary because instances tend to be held by only one owner and don't need to guarantee threadsafe access to the instance.

你可以锁定任何引用类型的实例(例如,从Object继承,而不是像int或enums这样的值类型,而不是null),但是理解对象上的锁对访问没有固有影响是非常重要的。对于该对象,它仅与其他尝试相互作用以获取对同一对象的锁定。由类使用适当的锁定方案来保护对其成员变量的访问。有时实例可以通过锁定自己来保护对自己成员的多线程访问(例如,锁定(this){...}),但通常这不是必需的,因为实例往往只由一个所有者持有而不是需要保证对实例的线程安全访问。

More commonly, a class creates a private lock (eg. private readonly object m_Lock = new Object(); for separate locks within each instance to protect access to members of that instance, or private static readonly object s_Lock = new Object(); for a central lock to protect access to the class's static members). Josh has a more specific code example of using a lock. You then have to code the class to use the lock appropriately. In more complex cases you might even want to create separate locks for different groups of members, to reduce contention for different kinds of resources which aren't used together.

更常见的是,类创建私有锁(例如,私有只读对象m_Lock = new Object();用于每个实例中的单独锁以保护对该实例的成员的访问,或私有静态只读对象s_Lock = new Object(); for一个*锁,以保护对类的静态成员的访问)。 Josh有一个更具体的使用锁的代码示例。然后,您必须对类进行编码以适当地使用锁。在更复杂的情况下,您甚至可能希望为不同的成员组创建单独的锁,以减少对未使用的不同类型资源的争用。

So, to bring it back to your original question, a method which only accesses its own local variables and parameters would be thread-safe, because these exist in their own memory locations on the stack specific to the current thread, and can not be accessed elsewhere--unless you shared those parameter instances across threads before passing them.

因此,为了使它回到原始问题,只访问其自己的局部变量和参数的方法将是线程安全的,因为它们存在于特定于当前线程的堆栈上它们自己的内存位置,并且无法访问其他 - 除非您在传递它们之前在线程之间共享这些参数实例。

A non-static method which only accesses the instances own members (no static members)--and of course parameters and local variables--would not need to use locks in the context of that instance being used by a single owner (doesn't need to be thread-safe), but if instances were intended to be shared and wanted to guarantee thread-safe access, then the instance would need to protect access to its member variables with one or more locks specific to that instance (locking on the instance itself being one option)--as opposed to leaving it up to the caller to implement their own locks around it when sharing something not intended to be thread-safe shareable.

只访问实例自己的成员(没有静态成员)的非静态方法 - 当然还有参数和局部变量 - 不需要在单个所有者使用的该实例的上下文中使用锁(不是需要是线程安全的,但如果要共享实例并希望保证线程安全访问,那么实例需要使用一个或多个特定于该实例的锁来保护对其成员变量的访问(锁定在实例本身就是一个选项) - 而不是将它留给调用者在共享不希望线程安全可共享的东西时围绕它实现自己的锁。

Access to readonly members (static or non-static) which aren't ever manipulated is generally safe, but if the instance it holds is not itself thread-safe or if you need to guarantee atomicity across multiple manipulations of it, then you may need to protect all access to it with your own locking scheme as well. That's a case where it could be handy if the instance uses locking on itself, because you could simply get a lock on the instance across multiple accesses into it for atomicity, but you wouldn't need to do so for single accesses into it if it's using a lock on itself to make those accesses individually thread-safe. (If it's not your class, you'd have to know whether it locks on itself or is using a private lock which you can't access externally.)

访问那些从未被操作的只读成员(静态或非静态)通常是安全的,但是如果它所拥有的实例本身不是线程安全的,或者如果你需要保证多次操作它的原子性,那么你可能需要用你自己的锁定方案来保护对它的所有访问。如果实例使用锁定本身就可以很方便,因为你可以简单地通过多次访问来实现对实例的锁定以获得原子性,但是如果它是单独访问它就不需要这样做。在自身上使用锁来使这些访问单独是线程安全的。 (如果不是你的班级,你必须知道它是自己锁定还是使用你无法从外部访问的私人锁。)

And finally, there's access to changing static members (changed by the given method or by any others) from within an instance--and of course static methods which access those static members and could be called from anyone, anywhere, anytime--which have the biggest need to use responsible locking, without which are definitely not thread-safe and are likely to cause unpredictable bugs.

最后,可以从实例中访问更改静态成员(由给定方法或其他任何方式更改) - 当然还有访问这些静态成员的静态方法,可以随时随地从任何人调用 - 这些方法具有最大的需要使用负责任的锁定,没有它肯定不是线程安全的,并可能导致不可预测的错误。

When dealing with .NET framework classes, Microsoft documents in MSDN whether a given API call is thread-safe (eg. static methods of the provided generic collection types like List<T> are made thread-safe while instance methods may not be--but check specifically to be sure). The vast majority of the time (and unless it specifically says it's thread-safe), it's not internally thread-safe, so it's your responsibility to use it in a safe manner. And even when individual operations are implemented internally thread-safe, you still have to worry about shared and overlapping access by your code if it does anything more complex which needs to be atomic.

在处理.NET框架类时,Microsoft在MSDN中记录给定的API调用是否是线程安全的(例如,提供的通用集合类型(如List )的静态方法是线程安全的,而实例方法可能不是 - 但具体检查确定)。绝大多数时候(除非它特别说它是线程安全的),它不是内部线程安全的,所以你有责任以安全的方式使用它。即使在内部线程安全的情况下实现单个操作,如果代码执行任何需要原子的更复杂的操作,您仍然需要担心代码的共享和重叠访问。

One big caveat is iterating over a collection (eg. with foreach). Even if each access to the collection gets a stable state there's no inherent guarantee that it won't change in between those accesses (if anywhere else can get to it). When the collection is held locally there's generally no problem, but a collection which could be changed (by another thread or during your loop's execution!) could produce inconsistent results. One easy way to solve this is to use an atomic thread-safe operation (inside your protective locking scheme) to make a temporary copy of the collection (MyType[] mySnapshot = myCollection.ToArray();) and then iterate over that local snapshot copy outside the lock. In many cases this avoids the need for holding a lock the whole time, but depending on what you're doing within the iteration this may not be enough and you just have to protect against changes the whole time (or you may already have it inside a locked section guarding against access to change the collection along with other things, so it's covered).

一个重要的警告是迭代一个集合(例如,使用foreach)。即使对集合的每次访问都获得稳定状态,也没有固有的保证,它不会在这些访问之间发生变化(如果其他任何地方都可以访问它)。当集合在本地保存时通常没有问题,但是可以更改的集合(通过另一个线程或在循环执行期间!)可能会产生不一致的结果。解决此问题的一种简单方法是使用原子线程安全操作(在保护性锁定方案内)来创建集合的临时副本(MyType [] mySnapshot = myCollection.ToArray();)然后迭代该本地快照在锁外复制。在许多情况下,这避免了一直持有锁的需要,但是根据你在迭代中所做的事情,这可能是不够的,你只需要在整个时间内防止变化(或者你可能已经在里面一个锁定的部分,防止访问改变集合以及其他东西,所以它被覆盖)。

So, there's a bit of an art to thread-safe design, and knowing just where and how to get locks to protect things depends a lot on the overall design and usage of your class(es). It can be easy to get paranoid and think you have to get locks all over for everything, but really it's about finding the right layer at which to protect things.

因此,线程安全设计有一点艺术,并且知道锁定以保护事物的位置和方法在很大程度上取决于您的类的整体设计和使用。可能很容易变得偏执,并认为你必须为所有事情获得锁定,但实际上它是关于找到保护事物的正确层。

#4


Your method is fine since it is only using local variables, let's change your method a bit:

你的方法很好,因为它只使用局部变量,让我们稍微改变你的方法:

static int foo;

static int addOne(int someNumber)
{
  foo=someNumber; 
  return foo++;
}

This is not a thread safe method because we are touching static data. This would then need to be modified to be:

这不是一个线程安全的方法,因为我们正在触摸静态数据。然后需要将其修改为:

static int foo;
static object addOneLocker=new object();
static int addOne(int someNumber)
{
  int myCalc;
  lock(addOneLocker)
  {
     foo=someNumber; 
     myCalc= foo++;
  }
  return myCalc;
}

Which I think this is a silly sample I just did cause if I'm reading it correctly there is no point in foo anymore but hey it's a sample.

我认为这是一个愚蠢的样本,如果我正确地读它就会导致foo没有任何意义,但是嘿,这是一个样本。

#5


There is some research going on which allows you to detect non-thread-safe code. E.g. the project CHESS at Microsoft Research.

有一些研究允许您检测非线程安全的代码。例如。微软研究院的CHESS项目。

#6


This would only be a race condition if it were modifying some variable external to the function. Your example is not doing that.

如果它正在修改函数外部的某个变量,那么这只会是竞争条件。你的例子不是那样做的。

That's basically what you're looking out for. Thread safe means that the function either:

这基本上就是你所期待的。线程安全意味着该功能:

  1. Does not modify external data, or
  2. 不修改外部数据,或

  3. Access to external data is properly synchronized so that only one function can access it at any one time.
  4. 正确同步对外部数据的访问,以便任何时候只有一个功能可以访问它。

External data could be something held in storage (database/file), or something internal to the application (a variable, an instance of a class, etc): basically anything that is declared anywhere in the world that is outside of the function's scope.

外部数据可以是存储(数据库/文件)中的东西,也可以是应用程序内部的东西(变量,类的实例等):基本上在世界任何地方声明的任何超出函数范围的东西。

A trivial example of an un-thread safe version of your function would be this:

您的函数的非线程安全版本的一个简单示例是:

private int myVar = 0;

private void addOne(int someNumber)
{
   myVar += someNumber;
}

If you call this from two different threads without synchronization, querying the value of myVar will be different depending on whether the query happens after all calls to addOne are complete, or the query happens in between the two calls, or the query happens before either of the calls.

如果你在没有同步的情况下从两个不同的线程调用它,查询myVar的值将会有所不同,具体取决于在所有对addOne的调用完成后查询是否发生,或者查询是在两次调用之间发生,还是查询发生在两者之前电话。

#7


In the above example no.

在上面的例子中没有。

Thread safety is mainly to do with stored state. You can make the above example non thread safe by doing this:

线程安全主要与存储状态有关。您可以通过执行以下操作使上面的示例非线程安全:

static int myInt;

static int addOne(int someNumber){
myInt = someNumber;
return myInt +1; 
}

This will mean that due to context switching thread 1 might get to the call myInt = someNumber and then context switch, lets say thread 1 just set it to 5. Then imagine that thread 2 comes in and uses 6 and returns 7. Then when thread 1 wakes up again it will have 6 in myInt instead of the 5 that it was using and return 7 instead of the expected 6. :O

这意味着由于上下文切换线程1可能会调用myInt = someNumber然后上下文切换,让我们说线程1只是将它设置为5.然后想象线程2进来并使用6并返回7.然后当线程1再次唤醒它将在myInt中有6而不是它正在使用的5并且返回7而不是预期的6.:O

#8


Anywhere, thread safe means that you don't have two or more threads colliding when you are accessing a resource. Usually static varaiables --- in languages like C#, VB.NET and Java --- made your code thread unsafe.

在任何地方,线程安全意味着在访问资源时没有两个或多个线程发生冲突。通常静态变量 - 用C#,VB.NET和Java等语言 - 使你的代码线程不安全。

In Java exists the synchronized keyword. But in .NET you get the assembly option/directive:

在Java中存在synchronized关键字。但是在.NET中你得到汇编选项/指令:


class Foo
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Bar(object obj)
    {
        // do something...
    }
}

Examples of non thread safe classes should be singletons, depending on how is this pattern coded. Usually it must implement a synchronized instance creator.

非线程安全类的示例应该是单例,具体取决于此模式的编码方式。通常它必须实现同步的实例创建者。

If you don't want a synchronized method, you can try locking method, such as spin-lock.

如果您不想使用synchronized方法,可以尝试使用锁定方法,例如自旋锁定。

#9


any access to an obect that can be used simultaneously by two threads is not threadsafe.

对两个线程可以同时使用的对象的任何访问都不是线程安全的。

your example in Part 2 is clearly safe, as it uses only values passed in as arguments, but if you used an object scoped variable you might have to surround the access with appropriate lock statements

第2部分中的示例显然是安全的,因为它仅使用作为参数传入的值,但如果您使用了对象范围变量,则可能必须使用适当的锁定语句来包围访问

#10


foo is not shared between concurrent or sequential invocations, so addOne is thread-safe.

并发或顺序调用之间不共享foo,因此addOne是线程安全的。

#11


The reason that 'foo' and 'someNumber' are safe in your example is that they reside on the stack, and each thread has their own stack and so are not shared.

在您的示例中,'foo'和'someNumber'是安全的原因是它们驻留在堆栈上,并且每个线程都有自己的堆栈,因此不会共享。

As soon as data has the potential to be shared, for example, being global or sharing pointers to objects, then you could have conflicts and may need to use locks of some sort.

一旦数据有可能被共享,例如,全局或共享指向对象的指针,那么您可能会遇到冲突,并且可能需要使用某种类型的锁。