从 2 个不同的线程写入共享资源,而不使用同步机制
我有以下代码(用 C# 编写,但可以轻松翻译为您喜欢的语言...)
class Program
{
static int sharedState = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(UpdateState);
Thread t2 = new Thread(UpdateState);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("sharedState value is {0}.", sharedState);
}
static void UpdateState()
{
for (int i = 0; i < 10; i++)
sharedState++;
}
}
正如您所猜到的,此代码创建两个工作线程,将共享状态值增加 1,持续 10 次。该代码在访问和写入共享值“sharedState”时没有任何同步机制(例如互斥锁、监视器或任何其他...),并且主线程正在等待两个工作线程完成其工作(Join)。 有人可以向我解释一下这段代码有什么问题吗?最后是否有可能 - 'sharedState' 的值将是 2? (我的一个朋友被问到这个问题,他们告诉他这是可能的,我们都无法理解如何......) 顺便说一句 - 当我运行这段代码时,我每次都会得到 20...
I have the following code (written in c#, but can easily be translated to your prefered language...)
class Program
{
static int sharedState = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(UpdateState);
Thread t2 = new Thread(UpdateState);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("sharedState value is {0}.", sharedState);
}
static void UpdateState()
{
for (int i = 0; i < 10; i++)
sharedState++;
}
}
As you anyone can guess, this code creates two workers threads that increment the shared state value by one for 10 time. The code does not have any synchronization mechanism (such as mutex, monitor or any other...) while accessing and writing the shared value 'sharedState', and the main thread is waiting for the two workers to finish their job (Join).
Does some one can please explain to me what is the problem with this code, and is it possible that at the end - the value of 'sharedState' will be 2? (a friend of mine was asked this question, and they told him that it is possible, and we both cant understand how...)
BTW - when I run this code I get 20 every time...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
线程的寿命不够长。在如此短的循环中,它们同时运行的可能性很小,尤其是在多核计算机上。当您启动第二个线程时,第一个线程已经完成。因为它只需要几分之一微秒,比启动线程所需的时间要少得多。让他们循环至少一千万次,以增加赔率并观看比赛。在发布版本中我的机器上的输出:
每次运行时都有不同的值。
The threads don't live long enough. The odds that they'll run concurrently are small with such a short loop, especially on a multicore machine. By the time you start the 2nd thread, the 1st thread is already done. Since it takes but a fraction of a microsecond, much less than what it takes to get a thread started. Let them loop for at least ten million times to increase the odds and see the race. Output on my machine in the Release build:
With different values each time I run it.
它不是线程安全的,因为
在内部解释如下:
现在,如果此操作不是原子/同步的,您可以拥有更多线程同时从静态属性中获取值,但只会存储最后一个线程的修改。
如果您想要线程安全增量,您应该使用 Interlocked.Increment
It is not thread safe because
is interpreted internally like:
Now if this operation is not atomic / synchronized you can have more threads taking the value from the static property concurrently but only modification from the last thread will be stored.
If you want thread safe increment you should use
Interlocked.Increment
当然,这在实践中是极不可能的!
Of course, this is extremely unlikely in practice!
确实有问题。但只有当你非常频繁地运行循环时它才会发生:
假设状态为 1
线程a读取状态并获取1
线程 a 被操作系统暂停,b 正在运行
线程 b 读取状态并获取 1
线程b计算1+1并写入2
线程 b 读取 2
线程b计算2+1并写入3
...
线程a计算1+1并写入2
执行 3 个增量,状态已从 1 更改为 2
但您永远不会得到低于最低边界条件的结果。因此,如果您从 0 开始并且每个线程递增 10 倍,那么无论发生什么情况,您将始终获得 10 或更高。
对于同步:
Interlocked.Increment(参考任何内容)是你的朋友。它是使用汇编程序中的锁定前缀来实现的。这是安全增加值的最有效方法。
使用 CompareEcxchange 可以执行稍微复杂的操作。但lock(something) 也非常高效。
There is a problem, indeed. But it will only accour if you run the loop very very often:
assume the state is 1
thread a reads the state and gets 1
thread a is paused by the os and b is run
thread b reads the state and gets 1
thread b calculates 1+1 and writes 2
thread b reads 2
thread b calculates 2+1 and writes 3
...
thread a calculates 1+1 and writes 2
3 increments where executed and the state has changed from 1 to 2
But you will never ever get a result beeing lower than your lowest boundary condition. So if you start with 0 and each thread increments 10 times you will always get 10 or higher, no matter what happends.
For snychronisation:
Interlocked.Increment(ref whatever) is your friend. It is implemented using the lock-prefix in assembler. It is the most efficient way to safely increment a value.
Slightly more complex operations can be performed using CompareEcxchange. But lock(somthing) is very efficient, too.