Go Language Goroutines: Difference between revisions
Line 2: | Line 2: | ||
* [[Go Concurrency#Goroutines|Go Concurrency]] | * [[Go Concurrency#Goroutines|Go Concurrency]] | ||
=Overview= | =Overview= | ||
Goroutines are fundamental components of the concurrent programming model in Go. Goroutines and how they fit into Go concurrency are briefly explained in [[Go_Concurrency#Overview|Go Concurrency]], and a summary is available here: | |||
Revision as of 01:09, 16 January 2024
Internal
Overview
Goroutines are fundamental components of the concurrent programming model in Go. Goroutines and how they fit into Go concurrency are briefly explained in Go Concurrency, and a summary is available here:
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.
Model your problem space with goroutines, which are inherently concurrent.
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 that runs concurrently with other functions, which means it may or may not run in parallel 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.
Goroutines free us from having to think about our problem space in terms of parallelism and instead allow us to model problems closer to their natural level of concurrency: functions. The benefit of the more natural mapping between problem space and Go code is the likely increased amount of the problem space that is modeled in a concurrent manner. The problem we work on as developers are naturally concurrent more often than not, we'll naturally be writing concurrent code at a finer level of granularity that we perhaps would in other languages.
Goroutines are lightweight, and we normally won't have to worry about creating one. There are appropriate times to consider how many goroutines are running in your system, but doing so upfront is a premature optimization.
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:
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
Atomic Primitives
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
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:
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.