Thread.Sleep(0)不能像描述的那样起作用?

10

我目前正在阅读这篇优秀的文章,其中有如下文字:

Thread.Sleep(0)立即放弃线程当前的时间片,自愿地将CPU交出给其他线程。

我想测试一下这个方法,以下是我的测试代码:

static string s = "";

static void Main(string[] args)
{
    //Create two threads that append string s
    Thread threadPoints = new Thread(SetPoints);
    Thread threadNewLines = new Thread(SetNewLines);

    //Start threads
    threadPoints.Start();
    threadNewLines.Start();

    //Wait one second for threads to manipulate string s
    Thread.Sleep(1000);

    //Threads have an infinite loop so we have to close them forcefully. 
    threadPoints.Abort();
    threadNewLines.Abort();

    //Print string s and wait for user-input
    Console.WriteLine(s);
    Console.ReadKey();
}

threadPoints和threadNewLines函数的作用:

static void SetPoints()
{
    while(true)
    {
        s += ".";
    }
}

static void SetNewLines()
{
    while(true)
    {
        s += "\n";
        Thread.Sleep(0);
    }
}

如果我正确理解Thread.Sleep(0),输出应该类似于这样:

............        |
..............      |
................    | <- End of console
..........          |
.............       |
...............     |

但我得到的输出是:

....................|
....................|
....                |
                    |
                    |
....................|
....................|
.................   |
                    |

考虑到文章一开始提到的被许多程序员高度推荐,我只能假设我对Thread.Sleep(0)的理解是错误的。因此,如果有人能澄清一下,我将非常感激。


4
不确定这段代码的目的是什么,除了在没有锁定的情况下在多个线程中读写对象引用具有高度不可预测的结果之外,它并没有证明任何东西。您的测试代码并不有效,需要进行锁定以证明 Sleep(0) 的任何内容都是错误的。 - Hans Passant
@HansPassant 我认为一旦你调用Thread.Sleep(0),它会立即切换到另一个线程,这使得无法连续调用SetNewLines()。 - Jordy
1
你不知道不同线程的执行顺序会如何拼接。如果你想要控制它,那么你的代码需要以某种方式进行同步。当你调用 Thread.Sleep(0) 时,可能会有另一个线程写入屏幕,也可能不会。 - Jodrell
5
不,Sleep(0)并不能保证切换到另一个线程。它只是允许线程调度程序选择另一个线程。由于该线程是唯一准备运行的线程,因此选择再次运行该线程非常常见。如果要证明您的观点,则需要找到仅有一个处理器核心的机器。这在现今很难找到。 - Hans Passant
你正在多个线程上践踏s。你可能会错过很多更新。并非所有的写入都会显示出来。我不确定这是一个经过深思熟虑的实验 :) 不确定它展示了什么。 - usr
显示剩余3条评论
5个回答

2

thread.sleep(0) 的作用是释放 CPU,让其他线程得以运行,但并不意味着另一个线程不能成为当前线程。如果你想将上下文传递给另一个线程,请尝试使用某种信号。


啊,我明白了。我以为有一种等待列表,一旦线程获得时间片,它就会重新排队。但你的意思是处理器处理的下一个线程是(或多或少)随机的? - Jordy
1
下一个要运行的线程不应该是当前线程,如果有其他具有相同优先级的可运行线程。但它可能是另一个进程中的线程。或者,您可能拥有足够核心的CPU可以同时运行所有线程。 - Ben Voigt
1
在实践中,我认为这种情况不太可能发生。我认为更有可能是缓存问题。 - Dan Bryant
无论操作系统(或CLR)使用何种方法进行线程操作调度,我认为您不能依赖于其实现永远不会发生变化,因为这超出了您的控制范围。 - Steve

1
如果您将控制台的宽度扩展到当前宽度的5倍,那么您将看到预期的结果,即行未达到控制台宽度。问题在于一个时间片实际上非常长。因此,要在普通控制台中获得预期的效果,您必须减慢Points线程的速度,但不使用Sleep。请尝试以下代码而不是while (true)循环。
for (int i = 0;; i++)
{
  if (int % 10 == 0)
    s += ".";
}

为了进一步减缓线程的速度,请将数字10替换为更大的数字。

1
如果你能够使用一台只有单核/处理器的机器(或者可能是虚拟机),尝试在该机器上运行你的代码。你会惊讶于结果的差异。仅仅因为两个线程引用了相同的变量“s”,并不意味着它们实际上同时引用了相同的值,这是由于现代多核(甚至仅仅是并行流水线)CPU中可能发生的各种级别的缓存所导致的。如果你想看到yielding如何工作而不考虑缓存问题,尝试将每个 s+=表达式包裹在一个lock语句中。

我尝试了锁定,结果有所不同(无论如何都有更多的新行),但它仍然不像我期望的那样。得找一个单核处理器。 - Jordy

0
处理器接下来要处理的线程是随机线程,甚至可能是您刚刚调用Thread.Sleep(0)的同一个线程。为确保下一个线程不是同一个线程,您可以调用Thread.Yield()并检查其返回结果 - 如果操作系统有另一个可以运行的线程,则返回true,否则返回false。

-1
你(几乎)永远不应该中止线程。最佳实践是向它们发送终止信号(自杀)。
通常通过设置一些布尔变量来实现这一点,线程应检查其值以确定是否继续执行。
您正在设置名为“s”的字符串变量。您将遇到竞争条件。字符串不是线程安全的。您可以在锁中包装操纵它的操作或使用一个内置类型,该类型是线程安全的。
始终注意,在文档中,了解您使用的类型是否是线程安全的。
因此,由于您的程序不是线程安全的,您无法依赖于您的结果。如果您多次运行程序,我的猜测是您将获得不同的输出。
注意:当使用布尔值共享某些状态以取消线程时,请确保它标记为volatileJIT 可能会优化代码并永远不会查看其更改后的值。

3
string是线程安全的。他使用string句柄的方式则不太安全。 - Ben Voigt
据我所知,获取字符串值并写入一些新值不是线程安全的。 - Luis Filipe
3
这不是字符串对象上的操作,它会创建一个全新的字符串。 - Ben Voigt
1
我想到了“CancellationToken”http://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken.aspx - Jodrell
两个线程同时获取字符串值。最后写入的那个将覆盖之前的写入。如果我有错误,请告诉我。 - Luis Filipe
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接