简体   繁体   English

多线程和随机的奇怪行为

[英]Strange behavior with multithreading and random

Edit @ Dublicate: I know that an unsafe use of Thread is not recomented. 编辑@ Dublicate:我知道不会重新使用不安全的Thread。 My question is about the why, and not about if random is thread-safe. 我的问题是为什么,而不是有关random是否是线程安全的。 Thanks to the answers, helped me understandig it better! 多亏了答案,帮助我更好地理解了它!

I wrote a "gimmick" program that is supposed to display random chars with random colors (For- and Background) in a Console-Window using Math.Random() and Multithreading. 我编写了一个“ gi头”程序,该程序应该使用Math.Random()和Multithreading在控制台窗口中显示具有随机颜色(For和背景)的随机字符。 For more randomness I didn't made the program "thread safe". 为了获得更多的随机性,我没有使程序成为“线程安全的”。 (Additional Information: I originally wanted the program to display an Space-Invader in the center, I achieved that with Thread-Safety, and I know, multithreading is supposed to be thread safe but this is not what this question is about) (其他信息:我本来希望程序在中心显示一个Space-Invader,我是通过Thread-Safety实现的,并且我知道多线程应该是线程安全的,但这不是这个问题的意思。)

The output looks like this: 输出看起来像这样:

预期产量

The function of the program is like that: I have an Array in which all the Positions (X/Y) with colors and Char are stored. 程序的功能如下:我有一个数组,其中存储了所有带有颜色和Char的位置(X / Y)。 I have some Functions that change this array and I have some functions to display the array. 我有一些更改此数组的函数,还有一些显示该数组的函数。 I also got a function to return random chars and one to return random colors. 我也有一个返回随机字符的函数和一个返回随机颜色的函数。

Now the point that I don't get: Sometimes everything works as described, but sometimes the program starts to display only !-Chars (exclamation mark) but keeps the random colors and positions: 现在我不明白的是:有时一切都按说明工作,但是有时程序开始仅显示!-Chars(感叹号),但保留随机的颜色和位置:

随机颜色仅带有感叹号

Another time the program only shows the colors black and white but the chars keep being random: 程序又一次只显示黑色和白色,但字符仍然是随机的:

黑色和白色,随机字符

And sometimes it happens, that the program only displays !-Chars and only black and white: 有时会发生该程序仅显示!-Chars且仅显示黑白的情况:

黑白仅带有感叹号

What I could say is the following: 我能说的是:

My "Get a Random Char" function looks like that: 我的“获取随机字符”功能如下所示:

public static char GetChar()
{
    return (char)randomChar.Next(33, 150);
}

!-Char is Ascii-Char 33. That means if the Program get stucked the Random-Char-Function only returns the lowest Random-Char (== 33 == !) !-Char是Ascii-Char33。这意味着如果程序卡住,则Random-Char-Function仅返回最低的Random-Char(== 33 ==!)

I got something similar for the colors. 我的颜色也差不多。 I give an random-number between 0 and 16 to a function to get back a Console-Color: 我给函数提供0到16之间的随机数,以获取Console-Color:

private ConsoleColor SetColor(char ColorIndex)
{
    switch (ColorIndex)
    {
        case (char)1:
            return ConsoleColor.Black;
        case (char)2:
            return ConsoleColor.Blue;
        case (char)3:
            return ConsoleColor.Cyan;
        case (char)4:
            return ConsoleColor.DarkBlue;
        case (char)5:
            return ConsoleColor.DarkCyan;
        case (char)6:
            return ConsoleColor.DarkGray;
        case (char)7:
            return ConsoleColor.DarkGreen;
        case (char)8:
            return ConsoleColor.DarkMagenta;
        case (char)9:
            return ConsoleColor.DarkRed;
        case (char)10:
            return ConsoleColor.DarkYellow;
        case (char)11:
            return ConsoleColor.Gray;
        case (char)12:
            return ConsoleColor.Green;
        case (char)13:
            return ConsoleColor.Magenta;
        case (char)14:
            return ConsoleColor.Red;
        case (char)15:
            return ConsoleColor.White;
        case (char)16:
            return ConsoleColor.Yellow;
        default:
            return ConsoleColor.Black;
    }
}

//The call looks like that:
_Data[x, y, 1] = (char)random.Next(0, 16);

I know that random.Next(0, 16) will give a 15 as maximum number back, and 15 is the color white in the example. 我知道random.Next(0,16)将给定最大数字15,在示例中15是白色。 If I change 15 to red, the Program will display red instead of white, when the program gets stucked: 如果我将15更改为红色,则当程序被卡住时,程序将显示红色而不是白色:

如果白色变成红色

This means: When the program got stucked the Random-Char-Function always returns the lowest possible number (33) and the Random-Color-Function always returns the highest possible number (15). 这意味着:当程序卡住时,Random-Char-Function总是返回最低的数字(33),Random-Color-Function总是返回最高的数字(15)。

The question : Why is this behaviour? 问题 :为什么这样? Why is it just sometimes and not every time? 为什么只是有时而不是每次? And why it is every time after a different time of running? 为什么每次运行时间都不一样? Why is one random function always returning max-number and the other one always the min-number? 为什么一个随机函数总是返回最大数而另一个总是最小数?

My guess is, that his is because of the CPU predicting, but as I said, it's just a guess and I wonder how this works, and why it goes to different approaches (min/max). 我的猜测是,他的原因是CPU的预测,但是正如我所说,这只是一个猜测,我想知道这是如何工作的,以及为什么要采用不同的方法(最小/最大)。

Here are some of my Code, if needed I could post more Code: 这是我的一些代码,如果需要,我可以发布更多代码:

//Program-Start after initialising some stuff:
public AnotherOne()
{
    new Thread(DrawData).Start();
    new Thread(DrawDataLR).Start();
    new Thread(DrawDataRL).Start();
    new Thread(DrawDataTD).Start();
    new Thread(DrawDataDT).Start();
    new Thread(ChangeData).Start();
    ChangeData();
}

//Draw Data example for Left-Right:
private void DrawDataLR()
{
    while (!_done)
    {
        DrawDataLeftRight();
        Thread.Sleep(2);
    }
}

private void DrawDataLeftRight()
{
    for (int x = 0; x < _Width - 1; x++)
    {
        for (int y = 0; y < _Height - 1; y++)
        {
            Console.SetCursorPosition(x, y);
            Console.BackgroundColor = SetColor(_Data[x, y, 1]);
            Console.ForegroundColor = SetColor(_Data[x, y, 2]);
            Console.WriteLine(_Data[x, y, 0]);
        }
    }
}

//Draw Data Function:
private void DrawData()
{
    Random next = new Random();
    int x = 1;

    while (!_done)
    {
        x = next.Next(1, 5);

        switch (x)
        {
            case 1:
                DrawDataLeftRight();
                break;
            case 2:
                DrawDataTopDown();
                break;
            case 3:
                DrawDataRightLeft();
                break;
            case 4:
                DrawDataDownTop();
                break;
        }
    }
}

//Change Data Function with "stripes" as Example:
private void ChangeData()
{
    int x = 100;

    while (!_done)
    {
        FillRandomFeld();
        Thread.Sleep(x);
        ClearChar();
        Thread.Sleep(x);
        SetColor();
        Thread.Sleep(x);
        Stripes();
        Thread.Sleep(x);
        SetChar();
        Thread.Sleep(x);
        OtherStripes();
        Thread.Sleep(x);

        x = randomX.Next(0, 100);
    }
}

private void Stripes()
{
    char colr = (char)random.Next(0, 16);

    for (int x = 0; x < _Width - 1; x += 4)
    {
        for (int y = 0; y < _Height - 1; y++)
        {
            if (_Data[x, y, 3] != (char)1)
            {
                _Data[x, y, 1] = colr;
                _Data[x, y, 2] = colr;
            }
        }
    }
}

Random.Next() ends up calling this code (from the reference source ): Random.Next()最终(从参考源 )调用此代码:

  private int InternalSample() {
      int retVal;
      int locINext = inext;
      int locINextp = inextp;

      if (++locINext >=56) locINext=1;
      if (++locINextp>= 56) locINextp = 1;

      retVal = SeedArray[locINext]-SeedArray[locINextp];

      if (retVal == MBIG) retVal--;          
      if (retVal<0) retVal+=MBIG;

      SeedArray[locINext]=retVal;

      inext = locINext;
      inextp = locINextp;

      return retVal;
  }

Looking at this code, it is obvious that multithreaded access could do some very bad things. 看这段代码,很明显,多线程访问可能会做一些非常糟糕的事情。 For example, if inext and inextp end up containing the same value, then SeedArray[locINext]-SeedArray[locINextp]; 例如,如果inextinextp最终包含相同的值,则SeedArray[locINext]-SeedArray[locINextp]; will always result in 0, and Random.Next() will always return 0. 将始终为0, Random.Next()将始终返回0。

I'm sure you can start to imagine lots of other ways that multithreaded access to this code could mess things up. 我敢肯定,您可以开始想象出很多其他方式可以使对该代码的多线程访问变得混乱。

Conceptually, PRNGs internally have a large number of states, where each state corresponds to a possible output of the PRNG (although multiple states can have the same output), and they walk through each one of those states in a deterministic order. 从概念上讲,PRNG在内部具有大量状态,其中每个状态对应于PRNG的可能输出(尽管多个状态可以具有相同的输出),并且它们以确定的顺序遍历这些状态中的每个状态。 If you corrupt the internal state of the PRNG, it may start walking through a much smaller set of those states before looping back to the first one, so you get a small repeating set of "random" output numbers. 如果破坏了PRNG的内部状态,它可能会开始遍历这些状态中的一小部分,然后循环回到第一个状态,因此您会得到一小组重复的“随机”输出数字。

In another place in your code, you are creating a new Random instance each time the method is called. 在代码的另一个位置,每次调用该方法时,您都在创建一个新的Random实例。 As mentioned in the linked answer, this will result in many Random instances which return the same set of random numbers. 如链接答案中所述,这将导致许多Random实例返回同一组随机数。

Eric Lippert has an ongoing series of blog posts about improving the Random class: part 1 part 2 (and more to come). 埃里克·利珀特(Eric Lippert)正在进行一系列有关改善Random类的博客文章: 第1 部分,第2部分 (以及更多内容)。

Your multithreaded use of the Random object is corrupting its internal state. 您对Random对象的多线程使用正在破坏其内部状态。

You can reproduce the issue fairly easily using the following program. 您可以使用以下程序轻松重现该问题。 If you run it, after a while (or sometimes, immediately) it will start producing zeroes instead of random numbers. 如果运行它,一段时间(有时是立即)后,它将开始产生零而不是随机数。 You may have to run it several times to see the effect. 您可能必须运行几次才能看到效果。 Also, it goes wrong more often for a RELEASE build rather than a DEBUG build (which is not unusual for multithreading problems!). 而且,对于RELEASE构建而不是DEBUG构建,它更容易出错(这在多线程问题中并不罕见!)。

(Note: This was tested using .Net 4.7.2 on a 16 core processor. Results may vary for other systems.) (注意:这是在16核心处理器上使用.Net 4.7.2进行的测试。结果可能与其他系统不同。)

using System;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine();

            Random rng = new Random(12345);

            Parallel.Invoke(
                () => printRandomNumbers(rng),
                () => printRandomNumbers(rng),
                () => printRandomNumbers(rng));

            Console.ReadLine();
        }

        static void printRandomNumbers(Random rng)
        {
            while (rng.Next() != 0)
            {}

            while (true)
            {
                Console.WriteLine(rng.Next());
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM