9

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?

EMBLEM
  • 2,207
  • 4
  • 24
  • 32

4 Answers4

7

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.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • so the question `Why doesn't Go's LockOSThread lock this OS thread?` should be `why multiple goroutines run even if GOMAXPROCS set to 1` – Jiang YD May 25 '16 at 05:20
1

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 (
    "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
}

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.

creker
  • 9,400
  • 1
  • 30
  • 47
1

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.

K.Dᴀᴠɪs
  • 9,945
  • 11
  • 33
  • 43
0

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("This may run almost straight away if tho long routine uses a different thread")
}

To this:

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

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.

sorifiend
  • 5,927
  • 1
  • 28
  • 45
  • Additional reading: http://stackoverflow.com/questions/25361831/benefits-of-runtime-lockosthread-in-golang – sorifiend May 25 '16 at 02:33
  • 3
    But the documentation states that "**no** other goroutines can" execute in that thread. That's misleading documentation if anything. I do not intend to sleep or halt the OS thread, but prevent the goroutine on line 3 of main from executing. – EMBLEM May 25 '16 at 03:13
  • Yeah it is a little misleading. However if you read through the insert in the middle it does say "Until the calling goroutine exits -snip- no other goroutine can". The goroutine its talking about is referring to something else you run after using `LockOSThread()`. It should possibly have "can be run" at the end of that doc. – sorifiend May 25 '16 at 03:38
  • 1
    @EMBLEM, "prevent the goroutine on line 3 of main from executing." then `LockOSThread` will not help you. Documentation is not misleading. As you can see in my answer, no other goroutine is execetuted on the thread but nothing stops the runtime to execute goroutine on other thread. Documentation is fine but you should read about Go's scheduler and how it correlates with `GOMAXPROCS`. `LockOSThread` is for cases when some piece of code has to execute on a specific thread. And it does exactly that. – creker May 28 '16 at 09:32