2

这是我的代码:

    Thread _th1, _th2, _th3, _th4;
int _i;

    void thMethod()
    {
        while(_i < 100){
            Thread.Sleep((new Random()).Next(1,500));
            Application.DoEvents();
            Console.WriteLine(_i);
            _i++;
        }
    }

    private void button4_Click(object sender, EventArgs e)
    {
        _th1 = new Thread(new ThreadStart(thMethod));
        _th1.Start();
        _th2 = new Thread(new ThreadStart(thMethod));
        _th2.Start();
        _th3 = new Thread(new ThreadStart(thMethod));
        _th3.Start();
        _th4 = new Thread(new ThreadStart(thMethod));
        _th4.Start();
    }

我想要做的是,使用多线程从 0 到 99 执行 Console.WriteLine,并且延迟是随机的。

但是,我的代码打印重复的数字

好吧,这就是结果。

0 1 2 2 4 5 6 7 8 9 10 10 12 13 14 14 16 16 18 19 20 20 22 22 24 24 26 26 28 28 30 31 32 33 34 35 36 36 38 39 40 40 42 42 4 4 4 4 4 64 6 50 52 68 70 70 72 74 74 74 76 76 78 78 80 80 82 82 80 90 98 98 98 100 101 102

如您所见,打印了重复的数字,甚至没有停止在 99。

那么...如何更正此代码以使其正常运行?

====================== 我必须改变吗..

    void thMethod()
    {
        while(_i < 100){
            Thread.Sleep((new Random()).Next(1,500));
            Application.DoEvents();
            Interlocked.Increment(ref _i);
            Console.WriteLine(_i);
        }
    }

这?

4

6 回答 6

3

尽管您应该按照Interlocked.Increment其他帖子所述使用。这不是您遇到的确切问题。

您的数字超过 100 的原因是因为您增加值的行出现在睡眠之后。这会导致线程进入循环,但当它们写出值时,该值已经更改为 100 以上。

例如 t1、t2 和 t3 进入循环并等待 500ms,i 为 99。然后 t4 退出并将其递增到 100,现在 t1 将打印 100,t2 将打印 101,t3 将打印 102。

Interlocked.Increment 实际上不会解决问题,因为 Thread.Sleep 在实际写出值时仍会导致不一致。

要解决这个问题,您需要使用线程锁 声明一个要锁定的变量,因为您不能锁定 _i 因为它不是引用类型:

object obj = new object();

void thMethod()
{
    while (_i < 100)
    {
        lock (obj)
        {
            Console.WriteLine(_i);
            _i++;
        }
        Thread.Sleep((new Random()).Next(1, 500));
    }
}

这样您就不会得到重复项,并且该值将停止在 99。

于 2012-07-30T22:11:55.080 回答
1

您需要以线程安全的方式调用Interlocked.Increment增量i

于 2012-07-30T21:56:12.363 回答
1

在多线程程序中,具有多个写入者的共享资源需要某种同步机制。

发生的情况是多个线程正在访问该变量以进行打印,然后其中一个线程有机会增加它,也就是race condition.

要解决这个问题,您需要在关键部分增加变量:

    void thMethod()
    {
        while (_i < 100)
        {
            Thread.Sleep((new Random()).Next(1, 500));
            Application.DoEvents();
            lock(this)
            {
                Console.WriteLine(_i);
                _i++;
            }
        }
    }

现在这不会停止在 100,所以另一个修复将是:

        while (_i < 100)
        {
            Thread.Sleep((new Random()).Next(1, 500));
            Application.DoEvents();
            lock(this)
            {
                if (_i >= 100) break;
                Console.WriteLine(_i);
                _i++;
            }
        }
于 2012-07-30T21:58:37.163 回答
0

变量 _i 必​​须在 thMethod 中声明,而不是在全局级别。

于 2012-07-30T21:59:05.190 回答
0

除了使用未探索的 Monitor 之外,还有两个选项

如果您只是使用其中一种 Interlocked 方法递增一个整数类型,则可以提供最佳性能。由于您还使用此变量来表示完成Interlocked.CompareExchange是很自然的。该技术用于无锁和无等待解决方案。

while (_i < 100)
{
    Thread.Sleep((new Random()).Next(1, 500));
    int initI;
    int modifiedI;
    bool valueUpdated = false;
    do
    {
        initI = _i;
        modifiedI = initI + 1;

        if (modifiedI > 100)
            break;

        valueUpdated =  (initI == Interlocked.CompareExchange(
                ref _i, modifiedI, initI) );

        if (valueUpdated)
            Console.WriteLine(modifiedI);

    } while (!valueUpdated)  ;


}

如果您为了简单起见,还可以使用 Parallel Class

Parallel.For(0, 100, i =>
    {
        Thread.Sleep((new Random()).Next(1, 500));
        _i++;
        Console.WriteLine("{0} | {1}" ,Thread.CurrentThread.ManagedThreadId, _i );
    }
);

出于演示目的,我添加了 ManagedThreadId 的输出,以便您可以看到多个线程正在使用中。

您应该注意,您失去了对创建的线程数的直接控制,而是可以通过设置MaxDegreeOfParallelism的 ParallelOption 来传递,这确实会影响线程数。

于 2012-07-31T16:24:23.347 回答
-1

我用联锁解决了这个问题。这是一个 vb.net 控制台应用程序,但很容易理解。每个线程都有其唯一的计数器,显然输出序列是无序的


模块模块1

Dim count As Integer = 0

Sub Main()
    For i = 1 To 10
        Dim th As New Thread(AddressOf ThreadedIncrement)
        th.Start()
    Next
    Console.ReadLine()
End Sub

Sub ThreadedIncrement()
    Dim r As New Random

    For i = 1 To 10
        Thread.SpinWait(r.Next(1000, 1000000))
        Dim ncount As Integer = Interlocked.Add(count, 1)
        Console.WriteLine(ncount.ToString)
    Next
End Sub

端模块


于 2014-10-24T18:06:14.210 回答