回答
当您在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之一(如lock
,Mutex
,ReaderWriterLockSlim
等),所以只有一个线程可以在它的工作一次。
2)将写入操作推入队列并使用单个线程处理该队列。正如Thorarin指出的那样,如果它不是线程安全的,您仍然必须同步队列的访问权限,但它对于复杂的写入操作是值得的。
还有其他技术。有些(如Interlocked
)仅限于特定的数据类型,甚至更多(如Non-blocking synchronization和Part 4 of Joseph Albahari's Threading in C#中讨论的那些),尽管它们更复杂:小心处理它们。
在多线程应用程序中,很多情况下同时访问相同的数据会导致问题。在这种情况下,需要同步来确保一次只有一个线程可以访问。
我想他们的意思是使用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
感谢您的回答。我想知道更多关于.NET中的多线程(C#)。事实上,尽管我一般需要更多地了解c#。你会建议我一本书,它可以将一个普通的开发人员转变为下一代开发人员。 – Supremestar 2010-01-22 15:03:50
没有特定的标题,但我不太多用书。事实上,我已经预先订购了第二版的C#,这真是太神奇了。好东西,但可能有些书更适合您的需求。 – Thorarin 2010-01-22 15:11:00
“C#深度”是一本很棒的书。 – 2010-01-22 15:18:48
有几种方法,其中有几种方法是前面提到的。
- ReaderWriterLockSlim是我的首选方法。这给你一个数据库类型的锁定,并允许升级(尽管我上次看到的MSDN语法是不正确的,而且非常不明显)
- 锁定语句。您将读取视为写入操作,只是防止访问变量
- 联锁操作。这在原子步骤中对值类型执行操作。这可以用于无锁的线程(真的不推荐这)
- 互斥和信号灯(没有使用这些)
- 监控报表(这基本上是锁定的关键字是如何工作的)
虽然我不想诋毁其他答案,但我不相信任何不使用这些技术的东西。如果我忘记了任何,我很抱歉。
- 1. .NET线程问题
- 2. c#线程访问问题
- 3. C#.NET线程问题
- 4. .NET线程 - 快速问题
- 5. .NET线程问题,TcpClient
- 6. Dispatcher.Invoke和线程访问的问题
- 7. C#线程 - 父访问问题
- 8. 问题:访问线程内的向量
- 9. 无效的跨线程访问问题
- 10. 多线程数据库访问(.NET)
- 11. Facebook离线访问.NET SDK
- 12. 访问线程
- 13. c#显示线程无效的跨线程访问问题
- 14. 多线程.NET队列问题
- 15. 在.net中的线程问题
- 16. 线程问题
- 17. 线程问题
- 18. 线程问题
- 19. 线程问题
- 20. 线程问题
- 21. 线程问题
- 22. 线程问题
- 23. 线程问题
- 24. 线程问题
- 25. 线程问题
- 26. IBM MQSeries从.NET访问问题
- 27. 访问Ruby线程
- 28. 从线程访问
- 29. 课程访问问题
- 30. 线程安全访问数组和线程安全访问
ReadWriterLockSlim为+1。然而,我认为OP在完全理解这个概念之前需要更多地熟悉多线程编程。至于你的第二点:你需要同步队列,使问题复杂化。 – Thorarin 2010-01-22 14:18:10
真的够了,你的回答为这些概念提供了一个非常优秀的介绍(虽然我试图在更新中采取不同的方式)。关于队列,确实也必须同步,尽管有些情况下这种方法可以简化事情而不是使事情复杂化! – 2010-01-22 14:24:35