Go Language Goroutines: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 1: Line 1:
=TODO=
=Internal=
<font color=darkkhaki>Deplete into [[Go Concurrency]].</font>
* [[Go Concurrency#Goroutines|Go Concurrency]]


=Overview=
=Overview=

Revision as of 23:11, 2 January 2024

Internal

Overview

OS threads.

Logically, a goroutine is a function executing concurrently with many other functions in the program. Internally, the goroutine is a structure managed by the Go runtime that with a very small initial memory footprint (a few kilobytes) and that is managed by the runtime, which grows or shrinks the memory allocated for the stack automatically. The CPU overhead averages about three instructions per function call. It is practically al to create hundreds of thousands of goroutines in the same address space.

In Go, concurrency is implemented with goroutines. The language offers support for concurrent programming at syntactic level, with keywords like go, chan and select.

A goroutine is a function that is treated as an independent unit of work and runs concurrently with other functions. Functions executing as goroutines can be thought as the Go implementation of a thread. They are similar to threads, but use far less memory to use. They are lightweight, thousands or tens of thousands of them can be created. Many goroutines execute within a single O/S thread. From the O/S point of view, only one thread is scheduled. The goroutine schedule is done by the Go runtime scheduler. The Go runtime scheduler uses a logical processor. The goroutines scheduled on a logical processor are executing concurrently, not |in parallel. However, it is possible to have more than one logical processor, each logical processors can be mapped onto an O/S thread, which may be scheduled to work on different cores. In this case, things may become parallel.

A goroutine is always created automatically, to run the main() function.

Sharing mutable variables between goroutines is discouraged. If it has to be done, mutual exclusion mechanisms like sync.Mutex are available. Communication between threads should be done preferably with channels. This pattern comes from a paradigm called "communicating sequential processes" (CSP). CSP is a message-passing model that works by sending data between goroutines instead of locking data for mutual exclusion or synchronized access ("Do not communicate by sharing memory; instead, share memory by communicating").

The go statement gives access to concurrency. When a function is executed as a goroutine, it is treated as an independent unit of work that gets scheduled and then executed on a logical processor.

Creation and Invocation

To explicitly create a goroutine and schedule it, use the go keyword, by providing a function invocation.

func somefunc(i int) {
  ...
}

...

go somefunc(10)

The go invocation returns immediately to the next line, while the invoked function is executed on a different thread.

Any function invocation can be used to be sent to a goroutine:

go fmt.Printf("something")

Anonymous functions (lambdas) can also be executed as goroutines:

...
go func(s string) {
  ...
  fmt.Println(s)
  ...
}("test")

Note that this syntax only schedules a goroutine. It is not determined when it will be actually executed.

what happens with the result of the function?

Goroutines and main()

If the invocation is done from main(), the scheduler always seems to continue to execute main(), it does not preempt main() to executes the new goroutine. Also, given the fact that when main() exists, all other goroutines are forcibly terminated, unless there's a mechanism that ensures they will be executed, they might not be executed at all. It is bad practice to use time.Sleep() to preempt and delay main(), because we're making assumptions about timing, and these assumptions can be wrong, and also we're assuming that the scheduler will schedule the other goroutine, when the main goroutine goes to sleep.

Also see:

main()

Exiting

A goroutine exits when the code is complete.

When the main goroutine is complete, all other goroutines are forced to exit. It is said that those goroutines exit early.

Pausing

time.Sleep()

Synchronization

Synchronization restricts the scheduler's options but it is some times necessary when things in different goroutines must happen in a certain order. Go provides synchronization mechanisms for goroutines in the sync package: WaitGroup, Mutex, Once. . Unbuffered channels also provide synchronization between threads.

WaitGroup

WaitGroup

Mutex

Mutex

RWMutex

RWMutex

Once

Once

Atomic Primitives

atomic package

Communication

Communication between threads is essential for concurrent programming. Data can be sent to goroutines by simply providing it as arguments to the functions that are invoked with the go keyword. This only works when the goroutine is started, though. Another way to send data to goroutines is via channels. Goroutines can use channels to receive and send data to other goroutines.

Channels

Go Channels

Deadlock

The Go runtime detects a deadlock where all goroutines are locked. It does not detect partial deadlocks, when only a subset of goroutines are deadlocked.

Also see:

Concurrent (Parallel) Programming | Deadlock

Go Runtime Scheduler

The Go runtime scheduler manages all goroutines that are created and need processor time. It binds operating system threads to logic processors, which, in turn, execute goroutines. As goroutines are created, they are placed in the scheduler's global run queue. From there, they are added to a logical processor's run queue and executed. Depending on the scheduling algorithm, a running goroutine can be stopped and rescheduled at any time. When a goroutine makes a blocking system call, the scheduler will detach the thread from the processor and create a new thread to service that processor. When a goroutine makes a network I/O call, the goroutine is detached from the logical processor and moved to the runtime network poller. Once the poller indicates a read or write operation is ready, the goroutine is assigned back to a logical processor to handle the operation.

Logical Processor

Go logical processors execute goroutines. Each logical process is individually bound to a single operating system thread.

SetMaxThreads

The Go runtime limits each program to a maximum 10,000 threads by default. This value can be changed by setting SetMaxThreads in runtime/debug package.