Що саме робить Runtime.Gosched?


86

У версії до випуску go 1.5 веб-сайту Tour of Go є фрагмент коду, який виглядає так.

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

Результат виглядає так:

hello
world
hello
world
hello
world
hello
world
hello

Мене турбує те, що після runtime.Gosched()видалення програма більше не друкує "світ".

hello
hello
hello
hello
hello

Чому це так? Як це runtime.Gosched()впливає на виконання?

Відповіді:


143

Примітка:

Станом на Go 1.5, GOMAXPROCS встановлюється на кількість ядер апаратного забезпечення: golang.org/doc/go1.5#runtime , нижче вихідної відповіді до 1.5.


Коли ви запускаєте програму Go без зазначення змінної середовища GOMAXPROCS, програми Go планують виконуватися в одному потоці ОС. Однак, щоб програма здавалася багатопотоковою (саме для цього призначені програми, правда?), Планувальник Go іноді повинен перемикати контекст виконання, щоб кожна програма могла виконати свою роботу.

Як я вже говорив, коли змінна GOMAXPROCS не вказана, в середовищі виконання Go дозволяється використовувати лише один потік, тому неможливо перемикати контексти виконання, поки goroutine виконує якусь звичайну роботу, наприклад обчислення або навіть IO (що відображається на звичайні функції C). ). Контекст можна переключити лише тоді, коли використовуються примітиви одночасності Go, наприклад, коли ви вмикаєте кілька чанів, або (це ваш випадок), коли ви явно вказуєте планувальнику перемикати контексти - це те runtime.Gosched, для чого.

Отже, коротко, коли контекст виконання в одній програмі досягає Goschedвиклику, планувальнику доручається переключити виконання на іншу програму. У вашому випадку є дві програми, основна (яка представляє "основний" потік програми) і додаткова, та, яку ви створили go say. Якщо ви видалите Goschedвиклик, контекст виконання ніколи не перенесеться з першої програми на другу, отже, для вас не буде "світу". Коли Goschedвін присутній, планувальник передає виконання на кожній ітерації циклу з першої програми на другу і навпаки, тому у вас є чергування "привіт" та "світ".

FYI, це називається "багатозадачністю в кооперативі": програми регулярно передають контроль іншим програмам. Підхід, що застосовується в більшості сучасних ОС, називається "переважною багатозадачністю": потоки виконання не стосуються передачі управління; натомість планувальник прозоро перемикає контексти виконання на них. Кооперативний підхід часто використовується для реалізації `` зелених потоків '', тобто логічних паралельних підпрограм, які не пов'язують 1: 1 з потоками ОС - саме таким чином реалізовано середовище виконання Go та його програми.

Оновлення

Я згадав змінну середовища GOMAXPROCS, але не пояснив, що це таке. Пора це виправити.

Коли для цієї змінної встановлено додатне число N, час виконання Go зможе створити до Nвласних потоків, на яких будуть заплановані всі зелені потоки. Вроджений потік - це різновид потоку, який створюється операційною системою (потоки Windows, pthreads тощо). Це означає, що якщо Nзначення більше 1, можливо, програми будуть заплановані для виконання в різних власних потоках і, отже, працюватимуть паралельно (принаймні, до можливостей вашого комп'ютера: якщо ваша система базується на багатоядерному процесорі, це цілком ймовірно, що ці потоки будуть справді паралельними; якщо ваш процесор має одне ядро, то переважна багатозадачність, реалізована в потоках ОС, створить видимість паралельного виконання).

Можна встановити змінну GOMAXPROCS за допомогою runtime.GOMAXPROCS()функції замість попереднього встановлення змінної середовища. Використовуйте щось подібне у своїй програмі замість поточної main:

func main() {
    runtime.GOMAXPROCS(2)
    go say("world")
    say("hello")
}

У цьому випадку ви можете спостерігати цікаві результати. Можливо, ви отримаєте рядки "привіт" та "світ", надруковані з нерівномірним чергуванням, наприклад

hello
hello
world
hello
world
world
...

Це може статися, якщо програми планують розділяти потоки ОС. Фактично так працює попереджувальна багатозадачність (або паралельна обробка у випадку багатоядерних систем): потоки паралельні, а їх комбінований вихід є невизначеним. До речі, ви можете залишити або видалити Goschedдзвінок, здається, це не впливає, коли GOMAXPROCS перевищує 1.

Нижче наведено те, що я отримав за кілька запусків програми з runtime.GOMAXPROCSвикликом.

hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world

Дивіться, іноді результат виходить гарний, іноді ні. Індетермінізм у дії :)

Чергове оновлення

Схоже, що в нових версіях компілятора Go середовище виконання Go змушує goroutines поступатися не тільки використанням примітивів паралельності, але і системних викликів ОС. Це означає, що контекст виконання може перемикатися між програмами також під час викликів функцій вводу-виводу. Отже, в останніх компіляторах Go можна спостерігати недетерміновану поведінку, навіть коли GOMAXPROCS не встановлено або встановлено на 1.


Чудова робота ! Але я не зустрічав цю проблему під версією 1.0.3, wierd.
WoooHaaaa

1
Це правда. Я щойно перевірив це за допомогою go 1.0.3, і так, така поведінка не з'явилася: навіть з GOMAXPROCS == 1 програма працювала так, ніби GOMAXPROCS> = 2. Здається, в 1.0.3 планувальник був налаштований.
Володимир Матвєєв

Я думаю, що все змінилося, компілятор wrt go 1.4. Здається, приклад у питаннях про операційні системи створює потоки ОС, тоді як це (-> gobyexample.com/atomic-counters ) створює спільне планування. Будь ласка, оновіть відповідь, якщо це правда
tez

8
Починаючи з версії 1.5, GOMAXPROCS встановлює кількість ядер апаратного забезпечення: golang.org/doc/go1.5#runtime
thepanuto

1
@paulkon, чи Gosched()потрібно це, залежить від вашої програми, це не залежить від GOMAXPROCSвартості. Ефективність переважної багатозадачності над кооперативною також залежить від вашої програми. Якщо ваша програма пов'язана з операціями вводу-виводу, то багатозадачність у співпраці з асинхронним введенням-виведенням, ймовірно, буде ефективнішою (тобто матиме більшу пропускну здатність), ніж синхронний вхід-вивід на основі потоку; якщо ваша програма пов'язана з процесором (наприклад, довгі обчислення), то багатозадачність у співпраці буде набагато менш корисною.
Володимир Матвєєв

8

Винуватцем є кооперативне планування. Не поступаючись, інший (скажімо "світовий") горутин може законно отримати нульові шанси на виконання до / коли основне закінчується, що за специфікацією припиняє всі горутини - тобто. весь процес.


1
добре, так що runtime.Gosched()врожайність. Що це означає? Це повертає управління назад до основної функції?
Jason Yeo

5
У цьому конкретному випадку так. Як правило, він просить планувальника ввести та запустити будь-яку з "готових" програм у навмисно невизначеному порядку відбору.
zzzz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.