Jef*_*nal 13
当您在C#中更改数据时,可能会将看似单个操作的内容编译为多个指令.参加以下课程:
public class Number {
private int a = 0;
public void Add(int b) {
a += b;
}
}
Run Code Online (Sandbox Code Playgroud)
构建它时,您将获得以下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
Run Code Online (Sandbox Code Playgroud)
现在,假设你有一个Number对象,两个线程调用它的Add方法如下:
number.Add(2); // Thread 1
number.Add(3); // Thread 2
Run Code Online (Sandbox Code Playgroud)
如果您希望结果为5(0 + 2 + 3),则表示存在问题.您不知道这些线程何时执行其指令.在执行IL_0003之前IL_000a(实际更改成员变量),两个线程都可以执行(将零推送到堆栈),并且您得到:
a = 0 + 2; // Thread 1
a = 0 + 3; // Thread 2
Run Code Online (Sandbox Code Playgroud)
完成'胜利'并在流程结束时的最后一个线程a是2或3而不是5.
因此,您必须确保在另一组之前完成一组完整的指令.为此,您可以:
而它被写入1)锁定访问类的成员,使用很多的一个.NET同步原语(如lock,Mutex,ReaderWriterLockSlim,等),以便只有一个线程可以在同一时间进行这项工作.
2)将写入操作推送到队列中并使用单个线程处理该队列.正如Thorarin指出的那样,如果它不是线程安全的,你仍然必须同步对队列的访问,但是对于复杂的写操作来说它是值得的.
还有其他技术.有些(比如Interlocked)仅限于特定的数据类型,还有更多(比如非阻塞同步和Joseph Albahari在C#中的线程的第4部分中讨论的那些),尽管它们更复杂:谨慎处理它们.
Tho*_*rin 12
在多线程应用程序中,有许多情况下同时访问相同的数据可能会导致问题.在这种情况下,需要同步以保证在任何时候只有一个线程可以访问.
我想他们的意思是使用lock-statement(或SyncLock在VB.NET中)而不是使用Monitor.
您可能希望阅读此页面以获取示例并理解该概念.但是,如果您没有使用多线程应用程序设计的经验,如果您的新雇主让您参加测试,很可能会很快显现出来.这是一个相当复杂的主题,有许多可能的陷阱,如死锁.
可能还有其他选项,具体取决于成员变量的类型以及如何更改.例如,可以使用Interlocked .Increment方法来增加整数.
作为问题的练习和演示,尝试编写一个启动5个并发线程的应用程序,每个线程增加一个共享计数器一百万次.计数器的预期最终结果将是500万,但这(可能)不是你最终会得到的结果:)
编辑:自己快速实现(下载).样本输出:
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
Run Code Online (Sandbox Code Playgroud)