2010-01-22 69 views
6

您能描述两种同步在类成员上执行多线程写入访问的方法 吗?.NET线程访问问题

请任何人都可以帮助我这是什么意思,什么是正确的答案。

回答

13

当您在C#中更改数据时,看起来像单个操作的东西可能会编译成若干指令。看看下面的类:

public class Number { 
    private int a = 0; 
    public void Add(int b) { 
     a += b; 
    } 
} 

当你盖了,你会得到下面的IL代码:

IL_0000: nop 
IL_0001: ldarg.0 
IL_0002: dup 
// Pushes the value of the private variable 'a' onto the stack 
IL_0003: ldfld  int32 Simple.Number::a 
// Pushes the value of the argument 'b' onto the stack 
IL_0008: ldarg.1 
// Adds the top two values of the stack together 
IL_0009: add 
// Sets 'a' to the value on top of the stack 
IL_000a: stfld  int32 Simple.Number::a 
IL_000f: ret 

现在,假设你有一个Number对象和两个线程调用其Add方法是这样的:

number.Add(2); // Thread 1 
number.Add(3); // Thread 2 

如果您想要结果为5(0 + 2 + 3),则会出现问题。你不知道这些线程何时执行他们的指令。这两个线程可以执行IL_0003(推零压入堆栈),要么执行IL_000a之前(实际上改变的是成员变量),你会得到这样的:

a = 0 + 2; // Thread 1 
a = 0 + 3; // Thread 2 

最后一个线程来完成“胜利”,并在该过程结束,a是2或3而不是5.

因此,您必须确保一套完整的指令在另一套完成之前完成。要做到这一点,您可以:

1)锁定访问类的成员,而它的写入,使用许多.NET synchronization primitives之一(如lockMutexReaderWriterLockSlim等),所以只有一个线程可以在它的工作一次。

2)将写入操作推入队列并使用单个线程处理该队列。正如Thorarin指出的那样,如果它不是线程安全的,您仍然必须同步队列的访问权限,但它对于复杂的写入操作是值得的。

还有其他技术。有些(如Interlocked)仅限于特定的数据类型,甚至更多(如Non-blocking synchronizationPart 4 of Joseph Albahari's Threading in C#中讨论的那些),尽管它们更复杂:小心处理它们。

+0

ReadWriterLockSlim为+1。然而,我认为OP在完全理解这个概念之前需要更多地熟悉多线程编程。至于你的第二点:你需要同步队列,使问题复杂化。 – Thorarin 2010-01-22 14:18:10

+0

真的够了,你的回答为这些概念提供了一个非常优秀的介绍(虽然我试图在更新中采取不同的方式)。关于队列,确实也必须同步,尽管有些情况下这种方法可以简化事情而不是使事情复杂化! – 2010-01-22 14:24:35

12

在多线程应用程序中,很多情况下同时访问相同的数据会导致问题。在这种情况下,需要同步来确保一次只有一个线程可以访问。

我想他们的意思是使用lock-statement(或VB.NET中的SyncLock)与使用Monitor

您可能想要read this page的例子和理解的概念。但是,如果您对多线程应用程序设计没有经验,如果您的新雇主让您参加测试,它可能会很快变得明显。这是一个相当复杂的主题,有很多可能的陷阱,如deadlock

还有一个体面的MSDN page on the subject以及。

可能有其他选项,具体取决于成员变量的类型以及如何更改。递增整数例如可以通过Interlocked.Increment方法完成。

作为问题的练习和演示,请尝试编写一个启动5个并发线程的应用程序,每个线程增加一个共享计数器一百万次。该计数器的预期最终结果将是500万,但是(可能)不是你将以最终结果:)

编辑:自己做了一个快速实现(download)。示例输出:

Unsynchronized counter demo: 
expected counter = 5000000 
actual counter = 4901600 
Time taken (ms) = 67 

Synchronized counter demo: 
expected counter = 5000000 
actual counter = 5000000 
Time taken (ms) = 287 
+0

感谢您的回答。我想知道更多关于.NET中的多线程(C#)。事实上,尽管我一般需要更多地了解c#。你会建议我一本书,它可以将一个普通的开发人员转变为下一代开发人员。 – Supremestar 2010-01-22 15:03:50

+0

没有特定的标题,但我不太多用书。事实上,我已经预先订购了第二版的C#,这真是太神奇了。好东西,但可能有些书更适合您的需求。 – Thorarin 2010-01-22 15:11:00

+2

“C#深度”是一本很棒的书。 – 2010-01-22 15:18:48

0

有几种方法,其中有几种方法是前面提到的。

  1. ReaderWriterLockSlim是我的首选方法。这给你一个数据库类型的锁定,并允许升级(尽管我上次看到的MSDN语法是不正确的,而且非常不明显)
  2. 锁定语句。您将读取视为写入操作,只是防止访问变量
  3. 联锁操作。这在原子步骤中对值类型执行操作。这可以用于无锁的线程(真的不推荐这)
  4. 互斥和信号灯(没有使用这些)
  5. 监控报表(这基本上是锁定的关键字是如何工作的)

虽然我不想诋毁其他答案,但我不相信任何不使用这些技术的东西。如果我忘记了任何,我很抱歉。