Check if function is being called as goroutine or not

Issue

Is there any way to find out if a running function was called as goroutine or not?

I’ve read ‘go tour’ and I am interested in building a websocket server with golang, so I found this tutorial https://tutorialedge.net/golang/go-websocket-tutorial/

Now I’m wondering if wsEndpoint function from the tutorial is invoked as goroutine (e.g. go wsEndpoint(…)) or not.

I’ve tried to read http package documentation, but did not get clear picture, just a guess that the handler will be called with go routine. Is that true?

Solution

Every function is called from a goroutine, even the main() function (which is called the main goroutine).

And goroutines in Go have no identity. It does not matter which goroutine calls a function.

To answer your “original” question:

Is there any way to find out if a running function was called as goroutine or not?

If we define this as the function being called with the go statement or without that, then the answer is yes: we can check that.

But before we do: I would not use this information for anything. Don’t write code that depends on this, nor on which goroutine calls a function. If you need to access a resource concurrently from multiple goroutines, just use proper synchronization.

Basically we can check the call stack: the list of functions that call each other. If the function is at the top of that list, then it was called using go (check note at the end of the answer). If there are other functions before that in the call stack, then it was called without go, from another function (that places before in the call stack).

We may use runtime.Callers() to get the calling goroutine’s stack. This is how we can check if there are other functions calling “us”:

func f(name string) {
    count := runtime.Callers(3, make([]uintptr, 1))
    if count == 0 {
        fmt.Printf("%q is launched as new\n", name)
    }
}

Testing it:

func main() {
    f("normal")
    go f("with-go")

    func() { f("from-anon") }()
    func() { go f("from-anon-with-go") }()

    f2("from-f2")
    go f2("with-go-from-f2")

    f3("from-f3")
    go f3("with-go-from-f3")

    time.Sleep(time.Second)
}

func f2(name string) { f(name) }
func f3(name string) { go f(name) }

This will output (try it on the Go Playground):

"with-go" is launched as new
"from-anon-with-go" is launched as new
"from-f3" is launched as new
"with-go-from-f3" is launched as new

Note: basically there is a runtime.goexit() function on “top” of all call stacks, this is the top-most function running on a goroutine and is the “exit” point for all goroutines. This is why we skip 3 frames from the stack (0. is runtime.Callers() itself, 1. is the f() function, and the last one to skip is runtime.goexit()). You can check the full call stacks with function and file names+line numbers in this Go Playground. This doesn’t change the viability of this solution, it’s just that we have to skip 3 frames instead of 2 to tell if f() was called from another function or with the go statement.

Answered By – icza

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.