Чем занимается runtime golang
Что именно делает runtime.Gosched?
Результат выглядит так:
Меня беспокоит то, что при runtime.Gosched() удалении программа перестает печатать «мир».
Почему это так? Как runtime.Gosched() влияет на исполнение?
Заметка:
Когда вы запускаете программу Go без указания переменной среды GOMAXPROCS, горутины Go планируются для выполнения в одном потоке ОС. Однако, чтобы программа выглядела многопоточной (для чего нужны горутины, не так ли?), Планировщик Go должен иногда переключать контекст выполнения, чтобы каждая горутина могла выполнять свою часть работы.
Обновить
Я упомянул переменную среды GOMAXPROCS, но не объяснил, что это такое. Пора это исправить.
Можно установить переменную GOMAXPROCS с помощью runtime.GOMAXPROCS() функции вместо предварительной установки переменной среды. Используйте в своей программе что-то подобное вместо текущего main :
В этом случае можно наблюдать интересные результаты. Возможно, что вы получите напечатанные строки hello и world с неравномерным чередованием, например
Это может произойти, если горутины запланированы для разделения потоков ОС. Фактически, именно так работает вытесняющая многозадачность (или параллельная обработка в случае многоядерных систем): потоки параллельны, а их комбинированный вывод является неопределенным. Кстати, вы можете оставить или удалить Gosched вызов, похоже, это не действует, когда GOMAXPROCS больше 1.
Вот что я получил при нескольких запусках программы с runtime.GOMAXPROCS call.
Видите ли, иногда результат бывает красивым, иногда нет. Индетерминизм в действии 🙂
Еще одно обновление
Похоже, что в более новых версиях компилятора Go среда выполнения Go заставляет горутины уступать не только при использовании примитивов параллелизма, но и при системных вызовах ОС. Это означает, что контекст выполнения можно переключать между горутинами также при вызове функций ввода-вывода. Следовательно, в последних компиляторах Go можно наблюдать недетерминированное поведение, даже когда GOMAXPROCS не установлен или установлен в 1.
Go: многопоточность и параллельность
Люблю Go, люблю его хвалить (бывает даже, привираю слега), люблю о нем статьи. Прочитал статью “Go: Два года в продакшне”, потом комменты. Стало понятно, на хабре — оптимисты! Хотят верить в лучшее.
По умолчанию Go работает на одном потоке, используя свой шедулер и асинхронные вызовы. (У программиста создается ощущение многопоточности и параллельности.) В этом случае каналы работаю очень быстро. Но если указать Go использовать 2 и больше потока, то Go начинает использовать блокировки и производительность каналов может падать. Не хочется себя ограничивать в использовании каналов. Тем более, большинство сторонних библиотек при каждом удобном случае используют каналы. Поэтому часто эффективно запускать Go с одним потоком, как это сделано по умолчанию.
теперь давайте активируем все ядра, перекомментировав строки.
20 раз медленней? В чем подвох? Размер канала по умолчанию 1.
результат 4 потока
Всего в два раза медленней, но не всегда можно это использовать.
Пример “канал каналов”
результат 4 потока
Поэтому, если у вас 8 ядер и вы пишите сервер на Go, вам не стоит полностью полагаться на Go в распараллеливании программы, а может, запустить 8 однопоточных процессов, а перед ними балансировщик, который тоже можно написать на Go. У нас в продакшине был сервер, который при переходе с одно-ядерного сервера на 4х стал обрабатывать на 10% меньше запросов.
Что значат эти цифры? Перед нами стояла задача обрабатывать 3000 запросов в секунду в одном контексте (например, выдавать каждому запросу последовательно числа: 1, 2, 3, 4, 5… может, чуть сложней) и производительность 3000 запросов в секунду ограничивается в первую очередь каналами. С добавлением потоков и ядер производительность растет не так рьяно, как хотелось. 3000 запросов в секунду для Go — это некий предел на современном оборудовании.
Ночной Update: Как нельзя оптимизировать
Комменты из статьи “Go: Два года в продакшне” побудили меня написать эту статью, но комменты этой превзошли комменты первой.
Хабражитель cybergrind предложил следующую оптимизацию. Она очень понравилась уже 8 другим хабражителям. Не знаю, читали они код или может они дайверы и все делают интуитивно, но я поясню. Так статья станет более полной и информативной.
Вот код:
В чем суть этой оптимизации?
1. Добавлен канал ch3. Этот канал блокирует вторую гоурутину, до окончания первой гоурутины.
2. Так как вторая гоурутина не читает из канала ch1, то он блокирует первую гоурутину при заполнении. Поэтому ch1 увеличен до необходимого 1,000,000
То есть, код больше не параллелен, работает последовательно, а канал используется как массив. И конечно этот код не в состоянии использовать второе ядро. В контексте этого кода нельзя говорить о “идеальном ускорении в N раз“.
Главное, подобный код будет работать только с определенным изначально объемом данных и не способен работать постоянно, в живую обрабатывать сколь угодно долго информацию.
Update 2: Тесты на Go 1.1.2
тест номер один с буфером 1 (channel01.go)
Вывод: значительно лучше. Зачем ставить буфер 1 трудно представить, но возможно есть применение у такого буфера.
тест номер один с буфером 100 (channel01.go)
Вывод: в два раза хуже, чем версия 1.0.2
тест номер два (channel03.go)
Примерно так же как версия 1.0.2, но чуть лучше. 1:8 против 1:10
Package runtime
Overview ▸
Overview ▾
Package runtime contains operations that interact with Go’s runtime system, such as functions to control goroutines. It also includes the low-level type information used by the reflect package; see reflect’s documentation for the programmable interface to the run-time type system.
Environment Variables
The following environment variables ($name or %name%, depending on the host operating system) control the run-time behavior of Go programs. The meanings and use may change from release to release.
The GOGC variable sets the initial garbage collection target percentage. A collection is triggered when the ratio of freshly allocated data to live data remaining after the previous collection reaches this percentage. The default is GOGC=100. Setting GOGC=off disables the garbage collector entirely. The runtime/debug package’s SetGCPercent function allows changing this percentage at run time. See https://golang.org/pkg/runtime/debug/#SetGCPercent.
The GODEBUG variable controls debugging variables within the runtime. It is a comma-separated list of name=val pairs setting these named variables:
The net, net/http, and crypto/tls packages also refer to debugging variables in GODEBUG. See the documentation for those packages for details.
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. This package’s GOMAXPROCS function queries and changes the limit.
The GOTRACEBACK variable controls the amount of output generated when a Go program fails due to an unrecovered panic or an unexpected runtime condition. By default, a failure prints a stack trace for the current goroutine, eliding functions internal to the run-time system, and then exits with exit code 2. The failure prints stack traces for all goroutines if there is no current goroutine or the failure is internal to the run-time. GOTRACEBACK=none omits the goroutine stack traces entirely. GOTRACEBACK=single (the default) behaves as described above. GOTRACEBACK=all adds stack traces for all user-created goroutines. GOTRACEBACK=system is like “all” but adds stack frames for run-time functions and shows goroutines created internally by the run-time. GOTRACEBACK=crash is like “system” but crashes in an operating system-specific manner instead of exiting. For example, on Unix systems, the crash raises SIGABRT to trigger a core dump. For historical reasons, the GOTRACEBACK settings 0, 1, and 2 are synonyms for none, all, and system, respectively. The runtime/debug package’s SetTraceback function allows increasing the amount of output at run time, but it cannot reduce the amount below that specified by the environment variable. See https://golang.org/pkg/runtime/debug/#SetTraceback.
The GOARCH, GOOS, GOPATH, and GOROOT environment variables complete the set of Go environment variables. They influence the building of Go programs (see https://golang.org/cmd/go and https://golang.org/pkg/go/build). GOARCH, GOOS, and GOROOT are recorded at compile time and made available by constants or functions in this package, but they do not influence the execution of the run-time system.
Index ▸
Index ▾
Examples (Expand All)
Package files
Constants
Compiler is the name of the compiler toolchain that built the running binary. Known toolchains are:
GOARCH is the running program’s architecture target: one of 386, amd64, arm, s390x, and so on.
GOOS is the running program’s operating system target: one of darwin, freebsd, linux, and so on. To view possible combinations of GOOS and GOARCH, run «go tool dist list».
Variables
MemProfileRate controls the fraction of memory allocations that are recorded and reported in the memory profile. The profiler aims to sample an average of one allocation per MemProfileRate bytes allocated.
To include every allocated block in the profile, set MemProfileRate to 1. To turn off profiling entirely, set MemProfileRate to 0.
The tools that process the memory profiles assume that the profile rate is constant across the lifetime of the program and equal to the current value. Programs that change the memory profiling rate should do so just once, as early as possible in the execution of the program (for example, at the beginning of main).
func BlockProfile ¶ 1.1
BlockProfile returns n, the number of records in the current blocking profile. If len(p) >= n, BlockProfile copies the profile into p and returns n, true. If len(p) = n, GoroutineProfile copies the profile into p and returns n, true. If len(p) 1.7
KeepAlive marks its argument as currently reachable. This ensures that the object is not freed, and its finalizer is not run, before the point in the program where KeepAlive is called.
A very simplified example showing where KeepAlive is required:
Without the KeepAlive call, the finalizer could run at the start of syscall.Read, closing the file descriptor before syscall.Read makes the actual system call.
Note: KeepAlive should only be used to prevent finalizers from running prematurely. In particular, when used with unsafe.Pointer, the rules for valid uses of unsafe.Pointer still apply.
func LockOSThread ¶
LockOSThread wires the calling goroutine to its current operating system thread. The calling goroutine will always execute in that thread, and no other goroutine will execute in it, until the calling goroutine has made as many calls to UnlockOSThread as to LockOSThread. If the calling goroutine exits without unlocking the thread, the thread will be terminated.
All init functions are run on the startup thread. Calling LockOSThread from an init function will cause the main function to be invoked on that thread.
A goroutine should call LockOSThread before calling OS services or non-Go library functions that depend on per-thread state.
func MemProfile ¶
MemProfile returns a profile of memory allocated and freed per allocation site.
MemProfile returns n, the number of records in the current memory profile. If len(p) >= n, MemProfile copies the profile into p and returns n, true. If len(p) 0 but r.AllocBytes == r.FreeBytes. These are sites where memory was allocated, but it has all been released back to the runtime.
The returned profile may be up to two garbage collection cycles old. This is to avoid skewing the profile toward allocations; because allocations happen in real time but frees are delayed until the garbage collector performs sweeping, the profile only accounts for allocations that have had a chance to be freed by the garbage collector.
func MutexProfile ¶ 1.8
MutexProfile returns n, the number of records in the current mutex profile. If len(p) >= n, MutexProfile copies the profile into p and returns n, true. Otherwise, MutexProfile does not change p, and returns n, false.
Most clients should use the runtime/pprof package instead of calling MutexProfile directly.
func NumCPU ¶
NumCPU returns the number of logical CPUs usable by the current process.
The set of available CPUs is checked by querying the operating system at process startup. Changes to operating system CPU allocation after process startup are not reflected.
func NumCgoCall ¶
NumCgoCall returns the number of cgo calls made by the current process.
func NumGoroutine ¶
NumGoroutine returns the number of goroutines that currently exist.
func ReadMemStats ¶
ReadMemStats populates m with memory allocator statistics.
The returned memory allocator statistics are up to date as of the call to ReadMemStats. This is in contrast with a heap profile, which is a snapshot as of the most recently completed garbage collection cycle.
func ReadTrace ¶ 1.5
ReadTrace returns the next chunk of binary tracing data, blocking until data is available. If tracing is turned off and all the data accumulated while it was on has been returned, ReadTrace returns nil. The caller must copy the returned data before calling ReadTrace again. ReadTrace must be called from one goroutine at a time.
func SetBlockProfileRate ¶ 1.1
SetBlockProfileRate controls the fraction of goroutine blocking events that are reported in the blocking profile. The profiler aims to sample an average of one blocking event per rate nanoseconds spent blocked.
To include every blocking event in the profile, pass rate = 1. To turn off profiling entirely, pass rate 1.7
SetCgoTraceback records three C functions to use to gather traceback information from C code and to convert that traceback information into symbolic information. These are used when printing stack traces for a program that uses cgo.
The traceback and context functions may be called from a signal handler, and must therefore use only async-signal safe functions. The symbolizer function may be called while the program is crashing, and so must be cautious about using memory. None of the functions may call back into Go.
The context function will be called with a single argument, a pointer to a struct:
In C syntax, this struct will be
If the Context field is 0, the context function is being called to record the current traceback context. It should record in the Context field whatever information is needed about the current point of execution to later produce a stack trace, probably the stack pointer and PC. In this case the context function will be called from C code.
If the Context field is not 0, then it is a value returned by a previous call to the context function. This case is called when the context is no longer needed; that is, when the Go code is returning to its C code caller. This permits the context function to release any associated resources.
While it would be correct for the context function to record a complete a stack trace whenever it is called, and simply copy that out in the traceback function, in a typical program the context function will be called many times without ever recording a traceback for that context. Recording a complete stack trace in a call to the context function is likely to be inefficient.
The traceback function will be called with a single argument, a pointer to a struct:
In C syntax, this struct will be
The Context field will be zero to gather a traceback from the current program execution point. In this case, the traceback function will be called from C code.
Otherwise Context will be a value previously returned by a call to the context function. The traceback function should gather a stack trace from that saved point in the program execution. The traceback function may be called from an execution thread other than the one that recorded the context, but only when the context is known to be valid and unchanging. The traceback function may also be called deeper in the call stack on the same thread that recorded the context. The traceback function may be called multiple times with the same Context value; it will usually be appropriate to cache the result, if possible, the first time this is called for a specific context value.
If the traceback function is called from a signal handler on a Unix system, SigContext will be the signal context argument passed to the signal handler (a C ucontext_t* cast to uintptr_t). This may be used to start tracing at the point where the signal occurred. If the traceback function is not called from a signal handler, SigContext will be zero.
Buf is where the traceback information should be stored. It should be PC values, such that Buf[0] is the PC of the caller, Buf[1] is the PC of that function’s caller, and so on. Max is the maximum number of entries to store. The function should store a zero to indicate the top of the stack, or that the caller is on a different stack, presumably a Go stack.
Unlike runtime.Callers, the PC values returned should, when passed to the symbolizer function, return the file/line of the call instruction. No additional subtraction is required or appropriate.
On all platforms, the traceback function is invoked when a call from Go to C to Go requests a stack trace. On linux/amd64, linux/ppc64le, and freebsd/amd64, the traceback function is also invoked when a signal is received by a thread that is executing a cgo call. The traceback function should not make assumptions about when it is called, as future versions of Go may make additional calls.
The symbolizer function will be called with a single argument, a pointer to a struct:
In C syntax, this struct will be
The PC field will be a value returned by a call to the traceback function.
The first time the function is called for a particular traceback, all the fields except PC will be 0. The function should fill in the other fields if possible, setting them to 0/nil if the information is not available. The Data field may be used to store any useful information across calls. The More field should be set to non-zero if there is more information for this PC, zero otherwise. If More is set non-zero, the function will be called again with the same PC, and may return different information (this is intended for use with inlined functions). If More is zero, the function will be called with the next PC value in the traceback. When the traceback is complete, the function will be called once more with PC set to zero; this may be used to free any information. Each call will leave the fields of the struct set to the same values they had upon return, except for the PC field when the More field is zero. The function must not keep a copy of the struct pointer between calls.
When calling SetCgoTraceback, the version argument is the version number of the structs that the functions expect to receive. Currently this must be zero.
The symbolizer function may be nil, in which case the results of the traceback function will be displayed as numbers. If the traceback function is nil, the symbolizer function will never be called. The context function may be nil, in which case the traceback function will only be called with the context field set to zero. If the context function is nil, then calls from Go to C to Go will not show a traceback for the C portion of the call stack.
SetCgoTraceback should be called only once, ideally from an init function.
func SetFinalizer ¶
SetFinalizer sets the finalizer associated with obj to the provided finalizer function. When the garbage collector finds an unreachable block with an associated finalizer, it clears the association and runs finalizer(obj) in a separate goroutine. This makes obj reachable again, but now without an associated finalizer. Assuming that SetFinalizer is not called again, the next time the garbage collector sees that obj is unreachable, it will free obj.
SetFinalizer(obj, nil) clears any finalizer associated with obj.
The argument obj must be a pointer to an object allocated by calling new, by taking the address of a composite literal, or by taking the address of a local variable. The argument finalizer must be a function that takes a single argument to which obj’s type can be assigned, and can have arbitrary ignored return values. If either of these is not true, SetFinalizer may abort the program.
Finalizers are run in dependency order: if A points at B, both have finalizers, and they are otherwise unreachable, only the finalizer for A runs; once A is freed, the finalizer for B can run. If a cyclic structure includes a block with a finalizer, that cycle is not guaranteed to be garbage collected and the finalizer is not guaranteed to run, because there is no ordering that respects the dependencies.
The finalizer is scheduled to run at some arbitrary time after the program can no longer reach the object to which obj points. There is no guarantee that finalizers will run before a program exits, so typically they are useful only for releasing non-memory resources associated with an object during a long-running program. For example, an os.File object could use a finalizer to close the associated operating system file descriptor when a program discards an os.File without calling Close, but it would be a mistake to depend on a finalizer to flush an in-memory I/O buffer such as a bufio.Writer, because the buffer would not be flushed at program exit.
It is not guaranteed that a finalizer will run if the size of *obj is zero bytes.
It is not guaranteed that a finalizer will run for objects allocated in initializers for package-level variables. Such objects may be linker-allocated, not heap-allocated.
A finalizer may run as soon as an object becomes unreachable. In order to use finalizers correctly, the program must ensure that the object is reachable until it is no longer required. Objects stored in global variables, or that can be found by tracing pointers from a global variable, are reachable. For other objects, pass the object to a call of the KeepAlive function to mark the last point in the function where the object must be reachable.
For example, if p points to a struct, such as os.File, that contains a file descriptor d, and p has a finalizer that closes that file descriptor, and if the last use of p in a function is a call to syscall.Write(p.d, buf, size), then p may be unreachable as soon as the program enters syscall.Write. The finalizer may run at that moment, closing p.d, causing syscall.Write to fail because it is writing to a closed file descriptor (or, worse, to an entirely different file descriptor opened by a different goroutine). To avoid this problem, call runtime.KeepAlive(p) after the call to syscall.Write.
A single goroutine runs all finalizers for a program, sequentially. If a finalizer must run for a long time, it should do so by starting a new goroutine.
func SetMutexProfileFraction ¶ 1.8
SetMutexProfileFraction controls the fraction of mutex contention events that are reported in the mutex profile. On average 1/rate events are reported. The previous rate is returned.
To turn off profiling entirely, pass rate 0. To just read the current rate, pass rate 1 the details of sampling may change.)
func Stack ¶
Stack formats a stack trace of the calling goroutine into buf and returns the number of bytes written to buf. If all is true, Stack formats stack traces of all other goroutines into buf after the trace for the current goroutine.
func StartTrace ¶ 1.5
func StopTrace ¶ 1.5
StopTrace stops tracing, if it was previously enabled. StopTrace only returns after all the reads for the trace have completed.
func ThreadCreateProfile ¶
ThreadCreateProfile returns n, the number of records in the thread creation profile. If len(p) >= n, ThreadCreateProfile copies the profile into p and returns n, true. If len(p) 1.1
BlockProfileRecord describes blocking events originated at a particular call sequence (stack trace).
type Error ¶
The Error interface identifies a run time error.
type Frame ¶ 1.7
Frame is the information returned by Frames for each call frame.
type Frames ¶ 1.7
Frames may be used to get function/file/line information for a slice of PC values returned by Callers.