为什么Go的LockOSThread不锁定这个操作系统线程?

huangapple go评论210阅读模式
英文:

Why doesn't Go's LockOSThread lock this OS thread?

问题

runtime.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秒钟,最后退出。为什么会发生这种情况呢?

英文:

The documentation for runtime.LockOsThread states:

> LockOSThread wires the calling goroutine to its current operating system thread. Until the calling goroutine exits or calls UnlockOSThread, it will always execute in that thread, and no other goroutine can.

But consider this program:

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

The main goroutine is wired to the one available OS thread set by GOMAXPROCS, so I would expect that the goroutine created on line 3 of main will not run. But instead the program prints This shouldn't run, pauses for 1 second, and quits. Why does this happen?

答案1

得分: 7

runtime包文档中可以得知:

GOMAXPROCS变量限制了可以同时执行用户级Go代码的操作系统线程的数量。在执行Go代码的系统调用期间,被阻塞的线程数量不受GOMAXPROCS限制。

休眠的线程不计入GOMAXPROCS值为1,因此Go可以自由地运行另一个线程来执行fmt.Println的goroutine。

英文:

From the runtime package documentation:

> The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit.

The sleeping thread doesn't count towards the GOMAXPROCS value of 1, so Go is free to have another thread run the fmt.Println goroutine.

答案2

得分: 1

这是一个Windows示例,可能会帮助你理解正在发生的情况。它打印正在运行的goroutine的线程ID。由于使用了syscall,所以它只在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来防止runtime为睡眠的goroutine生成另一个线程。通道将阻塞并从运行队列中移除goroutine。如果你执行这段代码,你会看到线程ID是不同的。主goroutine锁定了一个线程,所以runtime必须生成另一个线程。

正如你已经知道的,GOMAXPROCS不能阻止runtime生成更多的线程。GOMAXPROCS更多地是关于可以并行执行goroutine的线程数。但是,对于等待syscall完成的goroutine,可能会创建更多的线程。

如果你移除runtime.LockOSThread(),你会看到线程ID是相等的。这是因为通道读取会阻塞goroutine,并允许runtime将执行让给另一个goroutine,而不会生成新的线程。这就是即使GOMAXPROCS为1时多个goroutine可以并发执行的方式。

英文:

Here's a Windows sample that will probably help you understand what's going on. It prints thread IDs on which goroutines are running. Had to use syscall so it works only on Windows. But you can easily port it to other systems.

package main

import (
    &quot;fmt&quot;
    &quot;runtime&quot;
    &quot;golang.org/x/sys/windows&quot;
)

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

I don't use sleep to prevent runtime from spawning another thread for sleeping goroutine. Channel will block and just remove goroutine from running queue. If you execute the code you will see that thread IDs are different. Main goroutine locked one of the threads so the runtime has to spawn another one.

As you already know GOMAXPROCS does not prevent the runtime from spawning more threads. GOMAXPROCS is more about the number of threads that can execute goroutines in parallel. But more threads can be created for goroutines that are waiting for syscall to complete, for example.

If you remove runtime.LockOSThread() you will see that thread IDs are equal. That's because channel read blocks the goroutine and allows the runtime to yield execution to another goroutine without spawning new thread. That's how multiple goroutines can execute concurrently even when GOMAXPROCS is 1.

答案3

得分: 1

GOMAXPROCS(1)会导致只有一个活动的M(操作系统线程)用于服务Go协程(G)。

在你的程序中有两个Go协程,一个是main,另一个是fmt.Println。由于main协程处于休眠状态,M可以自由地运行任何Go协程,这种情况下fmt.Println可以运行。

英文:

GOMAXPROCS(1) which causes you to have one ACTIVE M (OS thread) to be present to server the go routines (G).

In your program there are two Go routines, one is main, and the other is fmt.Println. Since main routine is in sleep, M is free to run any go routine, which in this case fmt.Println can run.

答案4

得分: 0

这看起来对我来说是正确的行为。据我了解,LockOSThread()函数只是将所有未来的Go调用绑定到单个操作系统线程,它不会使线程休眠或停止。

为了更清楚,LockOSThread()的唯一作用是关闭多线程,以便所有未来的Go调用都在单个线程上执行。这主要用于需要单个线程才能正常工作的图形API等情况。

再次编辑。

如果你将这个代码块:

func main() {
    runtime.GOMAXPROCS(6)
    //在这里插入长时间运行的例程。
    go fmt.Println("如果长例程使用不同的线程,这可能会几乎立即运行")
}

与这个代码块进行比较:

func main() {
    runtime.GOMAXPROCS(6)
    runtime.LockOSThread()
    //在这里插入长时间运行的例程。
    go fmt.Println("只有在上面的任务完成后,这个语句才会运行")
}

如果我们在上面指示的位置插入一个长时间运行的例程,那么第一个代码块如果长例程在新线程上运行,可能会几乎立即运行,但在第二个例子中,它将始终等待例程完成。

英文:

That looks like correct behavior to me. From what I understand the LockOSThread() function only ties all future go calls to the single OS thread, it does not sleep or halt the thread ever.

Edit for clarity: the only thing that LockOSThread() does is turn off multithreadding so that all future GO calls happen on a single thread. This is primarily for use with things like graphics API's that require a single thread to work correctly.

Edited again.

If you compare this:

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

To this:

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

If we insert a long running routine in the location indicated above then the first block may run almost straight away if the long routine runs on a new thread, but in the second example it will always have to wait for the routine to complete.

huangapple
  • 本文由 发表于 2016年5月25日 09:42:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/37426511.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定