多线程和随机的奇怪行为

时间:2019-02-07 10:39:25

标签: c# multithreading random console prediction

编辑@ Dublicate:我知道不建议再次使用不安全的Thread。我的问题是为什么,而不是有关random是否是线程安全的。多亏了答案,帮助我更好地理解了它!

我编写了一个“ gi头”程序,该程序应该使用Math.Random()和Multithreading在控制台窗口中显示具有随机颜色(For和Background)的随机字符。为了获得更多的随机性,我没有使程序成为“线程安全的”。 (其他信息:我本来希望程序在中心显示一个Space-Invader,我是通过Thread-Safety实现的,而且我知道多线程应该是线程安全的,但这不是这个问题的意思)

输出看起来像这样:

The expected Output

程序的功能如下:我有一个数组,其中存储了所有带有颜色和Char的位置(X / Y)。我有一些更改此数组的函数,还有一些显示该数组的函数。我也有一个返回随机字符的函数和一个返回随机颜色的函数。

现在我不明白的是:有时所有内容均按说明工作,但有时程序开始仅显示!-Chars(感叹号),但保留随机的颜色和位置:

Random colors with exclamation mark only

另一次程序只显示黑色和白色,但字符保持随机:

Black and white with random chars

有时,该程序仅显示!-Chars,只显示黑白:

Black and white with exclamation mark only

我能说的是以下内容:

我的“获取随机字符”功能如下:

<html>
	<head> 
		<title>Menu</title>
		<link rel="stylesheet" type="text/css" href="style5.css">
	</head>
	<body>
	<div class="menu">
			<ul class="mainmenu">
				<li class="heading"><a href="#">Home</a>
					<ul class="submenu">
						<li class="items"><a href="#">A</a></li>
						<li class="items"><a href="#">B</a></li>
						<li class="items"><a href="#">C</a></li>
						<li class="items"><a href="#">D</a></li>
						<li class="items"><a href="#">E</a></li>
					</ul>
				</li>
				<li class="heading"><a href="#">About</a>
					<ul class="submenu">
						<li class="items"><a href="#">A</a></li>
						<li class="items"><a href="#">B</a></li>
						<li class="items"><a href="#">C</a></li>
						<li class="items"><a href="#">D</a></li>
						<li class="items"><a href="#">E</a></li>
					</ul>
				</li>
				<li class="heading"><a href="#">Services</a>
					<ul class="submenu">
						<li class="items"><a href="#">A</a></li>
						<li class="items"><a href="#">B</a></li>
						<li class="items"><a href="#">C</a></li>
						<li class="items"><a href="#">D</a></li>
						<li class="items"><a href="#">E</a></li>
					</ul>
				</li>
				<li class="heading"><a href="#">Products</a>
					<ul class="submenu">
						<li class="items"><a href="#">A</a></li>
						<li class="items"><a href="#">B</a></li>
						<li class="items"><a href="#">C</a></li>
						<li class="items"><a href="#">D</a></li>
						<li class="items"><a href="#">E</a></li>
					</ul>
				</li>
				<li class="heading"><a href="#">Contact</a>
					<ul class="submenu">
						<li class="items"><a href="#">A</a></li>
						<li class="items"><a href="#">B</a></li>
						<li class="items"><a href="#">C</a></li>
						<li class="items"><a href="#">D</a></li>
						<li class="items"><a href="#">E</a></li>
					</ul>
				</li>
			</ul>
		</div>
	</body>
</html>

!-Char是Ascii-Char33。这意味着如果程序卡住,则Random-Char-Function仅返回最低的Random-Char(== 33 ==!)

我的颜色也差不多。我给函数提供0到16之间的随机数,以获取Console-Color:

Insert into Table 1
Select <columns> From Table 2
where (condition1) or (condition2)  or(condition3)

我知道random.Next(0,16)将给定最大数字15,在示例中15是白色。如果我将15更改为红色,则当程序被卡住时,程序将显示红色而不是白色:

If white is changed to red

这意味着:当程序卡住时,Random-Char-Function始终返回最低数(33),Random-Color-Function始终返回最高数(15)。

问题:为什么这种行为?为什么只是有时而不是每次?为什么每次运行时间都不一样?为什么一个随机函数总是返回最大数,而另一个总是最小数?

我的猜测是,他的原因是基于CPU的预测,但正如我所说,这只是一个猜测,我想知道这是如何工作的,以及为什么采用不同的方法(最小/最大)。

这是我的一些代码,如果需要,我可以发布更多代码:

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

2 个答案:

答案 0 :(得分:1)

Random.Next()最终调用了此代码(来自the reference source):

  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;
  }

看看这段代码,很明显,多线程访问可能会做一些非常糟糕的事情。例如,如果inextinextp最终包含相同的值,那么SeedArray[locINext]-SeedArray[locINextp];将始终为0,而​​Random.Next()将始终返回0。

我敢肯定,您可以开始想象出许多其他方法来对代码进行多线程访问可能会弄乱事情。

从概念上讲,PRNG在内部具有大量状态,其中每个状态对应于PRNG的可能输出(尽管多个状态可以具有相同的输出),并且它们以确定的顺序遍历这些状态中的每个状态。如果破坏PRNG的内部状态,它可能会开始遍历较小的一组状态,然后循环回到第一个状态,因此会得到一小组重复的“随机”输出数字。

在代码的另一个位置,每次调用该方法时,您都在创建一个新的Random实例。如链接答案中所述,这将导致许多Random实例返回相同的随机数集。

埃里克·利珀特(Eric Lippert)正在进行一系列有关改善Random类的博客文章:part 1 part 2(还有更多内容)。

答案 1 :(得分:1)

您对Random对象的多线程使用正在破坏其内部状态。

您可以使用以下程序轻松重现该问题。如果运行它,一段时间(有时是立即)后,它将开始产生零而不是随机数。您可能必须运行几次才能看到效果。此外,对于RELEASE构建而不是DEBUG构建,它更容易出错(对于多线程问题而言,这并不罕见!)。

(注意:这是在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());
            }
        }
    }
}