2

这很有趣......我想知道为什么会发生这种情况。

public int numCounter;

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  Thread myThread = new Thread(myMethod);
  myThread.Start();
 }
}

public void myMethod()
{
 Console.WriteLine(numCounter);
}

结果会因月相而异... 3 3 4 4 5 6 7 8 9 10

甚至:1 4 5 5 5 6 7 8 10 10

问题是……为什么会这样?如果线程在变量增加后启动,为什么它应该取一个未更新的值????

4

4 回答 4

2

仔细查看结果。问题不在于线程使用了未更新的值,而在于该值被更新了太多次

例如,在您的第二个示例中,事件可能是这样的:

Thread 1   Thread 2     Thread 3
Start T2
i++
           WriteLine(1)
Start T3
i++
Start T4
i++
Start T5
i++
                         WriteLine(4)

尽管由于编译器和 CPU 优化以及 CPU 缓存的原因,线程可能会变得更加复杂。

于 2012-05-14T16:48:56.363 回答
1

其他答案是正确的,但我想详细说明为什么会发生这种情况。只是说“它是不确定的”是正确的,但解释不够。

发生这种行为是因为写入处理器(您的“主”线程)在刷新到内存之前先写入其缓存行。其他线程,即读者,无法查看作者的缓存行。只有当内存刷新发生时,数据才会传播出去。

刷新到内存的速度与 CPU 的速度一样快,但如果更新连续快速发生,一些更新将被合并到单个存储中。这就是数字缺失的原因——它们的写入已被合并。

于 2012-05-14T18:42:42.700 回答
0

仅仅因为线程以更新的值开始并不能保证在它到达读取值的代码时该值保持不变。根据系统中发生的情况,在您点击 Console.WriteLine 之前,您可能会在循环中进行多次迭代。

如果您希望每个线程具有不同的值,那么最好的方法是在循环中声明一个整数,并将其初始化为 numCounter。然后在您的线程中使用 ParameterizedThreadStart,并将本地值传递给它。或者,我要做的一件事是声明一个委托(您仍然必须在此选项中使用范围为循环的变量)内联来完成工作,以避免整数的装箱/拆箱。

for (int i = 0; i < 10; i++){
    var tmp = i;
    new Thread(() => {
        Console.WriteLine(tmp);
    }).Start();
}

您还希望在非假设设置中跟踪线程的状态,这样您就不会在所有线程完成工作之前退出。如果您不想使用 ThreadPool,则声明一个与循环大小相同的 WaitHandles 数组,然后在线程代码完成执行后调用 WaitArray[tmp].Set()。在循环之后,您将有一个 WaitHandle.WaitAll(WaitArray) 语句阻塞,直到它们全部完成。不过,仅使用 ThreadPool 及其相关的回调机制可能更容易、更安全。

于 2012-05-14T18:43:31.623 回答
0

据我所知,这完全是不确定的。您可以从写入的十个零一直到十个十以及其间的所有递增变化一直获取值。

原因是您没有对numCounter变量进行任何同步和/或序列化的访问。线程只会在线程执行时读取它,这可能随时发生,基于任意数量的环境条件。

因此,获得所有数字且不重复的快速方法是:

public int numCounter;

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  Thread myThread = new Thread(myMethod);
  myThread.Start(numCounter);
 }
}

public void myMethod(object numCounter)
{
 Console.WriteLine(numCounter);
}

这里的主要缺点是 1) 的签名myMethod必须更改,2)numCounter从 an转换为intan时会发生装箱,object以及 3) 这仍然不能保证输出的顺序为预期的 0..10。

这是一个稍微不同的版本,它使用 adelegateBeginInvoke/ EndInvoke(我喜欢做线程的方式)。它从上面消除了 #2 的缺点,但仍保留 #1 和 #3:

public int numCounter;

private delegate void MyMethodDelegate(int numCounter);

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  MyMethodDelegate myDelegate = new MyMethodDelegate(myMethod);
  myDelegate.BeginInvoke(numCounter, myMethodDone, myDelegate);
 }
}

public void myMethod(int numCounter)
{
 Console.WriteLine(numCounter);
}

public void myMethodDone(IAsyncResult result)
{
 MyMethodDelegate myDelegate = result.AsyncState as MyMethodDelegate;

 if (myDelegate != null)
 {
  myDelegate.EndInvoke(result);
 }
}
于 2012-05-14T16:49:22.090 回答