为什么没有LockOSThread锁定这个操作系统线程?

时间:2016-05-25 01:42:35

标签: multithreading go

runtime.LockOsThread州的

The documentation

  

LockOSThread将调用goroutine连接到其当前操作系统线程。在调用goroutine退出或调用UnlockOSThread之前,它将始终在该线程中执行,而没有其他goroutine可以执行。

但考虑一下这个程序:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    runtime.GOMAXPROCS(1)
    runtime.LockOSThread()
    go fmt.Println("This shouldn't run")
    time.Sleep(1 * time.Second)
}

main goroutine连接到GOMAXPROCS设置的一个可用操作系统线程,所以我希望在main的第3行创建的goroutine不会运行。但是程序打印This shouldn't run,暂停1秒,然后退出。为什么会这样?

4 个答案:

答案 0 :(得分:6)

来自runtime package documentation

  

GOMAXPROCS变量限制了可以同时执行用户级Go代码的操作系统线程数。 代表Go代码在系统调用中可以阻止的线程数没有限制;那些不计入GOMAXPROCS限制。

睡眠线程不会计入GOMAXPROCS值1,因此Go可以让另一个线程运行fmt.Println goroutine。

答案 1 :(得分:1)

这是一个Windows示例,可能会帮助您了解正在发生的事情。它打印正在运行goroutine的线程ID。不得不使用系统调用,因此它只适用于Windows。但您可以轻松将其移植到其他系统。

package main

import (
    "fmt"
    "runtime"
    "golang.org/x/sys/windows"
)

func main() {
    runtime.GOMAXPROCS(1)
    runtime.LockOSThread()
    ch := make(chan bool, 0)
    go func(){
        fmt.Println("2", windows.GetCurrentThreadId())
        <- ch
    }()
    fmt.Println("1", windows.GetCurrentThreadId())
    <- ch
}

我不使用sleep来防止运行时产生另一个用于睡眠goroutine的线程。 Channel将阻止并从运行队列中删除goroutine。如果执行代码,您将看到线程ID不同。主goroutine锁定了其中一个线程,因此运行时必须生成另一个线程。

正如您所知,GOMAXPROCS不会阻止运行时产生更多线程。 GOMAXPROCS更多地是关于可以并行执行goroutine的线程数。但是,例如,可以为等待系统调用完成的goroutine创建更多线程。

如果删除runtime.LockOSThread(),您将看到线程ID相等。这是因为通道读取会阻止goroutine并允许运行时将执行权交给另一个goroutine而不会生成新线程。即使GOMAXPROCS为1,这也是多个goroutine可以同时执行的方式。

答案 2 :(得分:0)

这看起来对我来说是正确的行为。根据我的理解,LockOSThread()函数只将所有未来的调用绑定到单个OS线程,它不会休眠或暂停线程。

为了清晰起见编辑:LockOSThread()唯一做的是关闭多线程,以便所有未来的GO调用都在一个线程上发生。这主要用于需要单个线程正常工作的图形API等。

再次编辑。

如果你比较一下:

func main() {
    runtime.GOMAXPROCS(6)
    //insert long running routine here.
    go fmt.Println("This may run almost straight away if tho long routine uses a different thread")
}

对此:

func main() {
    runtime.GOMAXPROCS(6)
    runtime.LockOSThread()
    //insert long running routine here.
    go fmt.Println("This will only run after the task above has completed")
}

如果我们在上面指出的位置插入一个长时间运行的例程,那么如果长例程在新线程上运行,则第一个块几乎可以直接运行,但在第二个例子中,它总是必须等待例程完成

答案 3 :(得分:0)

GOMAXPROCS(1)使您拥有一个ACTIVE M(OS线程)来执行go例程(G)。

您的程序中有两个Go例程,一个是main,另一个是fmt.Println。由于main例程处于睡眠状态,因此M可以自由运行任何go例程,在这种情况下fmt.Println可以运行。