Параллельное программирование. Горутины

Горутины

Последнее обновление: 12.01.2018

Горутины (goroutines) представляют параллельные операции, которые могут выполняться независимо от функции, в которой они запущены. Главная особенность горутин состоит в том, что они могут выполняться параллельно. То есть на многоядерных архитектурах есть возможность выполнять отдельные горутины на отдельных ядрах процессора, тем самым горутины будут выполняться паралелльно, и программа завершится быстрее.

Каждая горутина, как правило, представляет вызов функции, и последовательно выполняет все свои инструкции. Когда мы запускаем программу на Go, мы уже работаем как минимум с одной горутиной, которая представлена функцией main. Эта функция последовательно выполняет все инструкции, которые определены внутри нее.

Для определения горутин применяется оператор go, который ставится перед вызовом функции:

go вызов_функции

Например, определим несколько горутин, вычисляющих факториал числа:

package main
import "fmt"

func main() {
	
	for i := 1; i < 7; i++{
		go factorial(i)
	}
	fmt.Println("The End")
}

func factorial(n int){
	if(n < 1){
		fmt.Println("Invalid input number")
		return
	}
	result := 1
	for i := 1; i <= n; i++{
		result *= i
	}
	fmt.Println(n, "-", result)
}

В цикле последовательно запускаются шесть горутин с помощью вызова go factorial(i). То есть фактически это обычный вызов функции с оператором go.

Однако вместо шести факториалов на консоли при вызове программы мы можем увидеть только строку "The End":

The End

"Можем увидеть" означает, что поведение программы в данном случае недетерминировано. Например, вывод может быть и таким:

2 - 2
1 - 1
4 - 24
The End
5 - 120

Почему так происходит? После вызова go factorial(i) функция main запускает горутину, которая начинает выполняться в своем контексте, независимо от функции main. То есть фактически main и factorial выполняются параллельно. Однако главной горутиной является вызов функции main. И если завершается выполнение этой функции, то завершается и выполнение всей программы. Поскольку вызовы функции factorial представляют горутины, то функция main не ждет их завершения и после их запуска продолжает свое выполнение. Какие-то горутины могут завершиться раньше функции main, и соответственно мы сможем увидеть на консоли их результат. Но может сложиться ситуация, что функция main выполнится раньше вызовов функции factorial. Поэтому мы можем не увидеть на консоли факториалы чисел.

Чтобы все таки увидеть результат выполнения горутин, поставим в конце функции main вызов функции fmt.Scanln(), которая ожидает ввода пользователя с консоли:

package main
import "fmt"

func main() {
	
	for i := 1; i < 7; i++{
		go factorial(i)
	}
	fmt.Scanln()		// ждем ввода пользователя
	fmt.Println("The End")
}

func factorial(n int){
	if(n < 1){
		fmt.Println("Unvalid input number")
		return
	}
	result := 1
	for i := 1; i <= n; i++{
		result *= i
	}
	fmt.Println(n, "-", result)
}

Теперь мы сможем увидеть результаты всех вызовов функции factorial:

1 - 1
3 - 6
5 - 120
4 - 24
2 - 2
6 - 720

The End

Стоит отметить, что так как каждая горутина запускается в своем собственном контексте и выполняется независимо и паралелльно по сравнению с другими горутинами, то в данном случае нельзя четко детерминировать, какая из горутин завершится раньше. Например, горутина go factorial(2) запускается до go factorial(5), однако может завершиться после.

Горутины также могут представлять вызовы анонимных функций:

package main
import "fmt"

func main() {
	
	for i := 1; i < 7; i++{
		
		go func(n int){
			result := 1
			for j := 1; j <= n; j++{
				result *= j
			}
			fmt.Println(n, "-", result)
		}(i)
	}
	fmt.Scanln()
	fmt.Println("The End")
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850