> For the complete documentation index, see [llms.txt](https://yuliyas-organization-3.gitbook.io/prokhodim-sobesedovanie-na-golang-razrabotchika/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yuliyas-organization-3.gitbook.io/prokhodim-sobesedovanie-na-golang-razrabotchika/golang-zadachi/zadachi-na-gorutiny.md).

# Задачи на горутины

### 1) Что выведет данная программа и почему?

```go
package main
import (
	"fmt"
	"sync"
)
	
func main() {
	var counter int
	var wg sync.WaitGroup
	wg.Add(1000)

	for i := 0; i < 1000; i++ {
		go func() {
			for j := 0; j < 100; j++ {
				counter++
			}
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(counter)
}
```

Данная программа предназначена для увеличения значения переменной `counter` в 1000 параллельно работающих горутинах. Каждая горутина увеличивает `counter` на 1 в цикле, который выполняется 100 раз. Таким образом, теоретически, после выполнения всех горутин, значение `counter` должно быть 100000 (1000 горутин \* 100 инкрементов).

Однако, в данной программе есть проблема синхронизации доступа к переменной `counter`, которая не защищена мьютексом или другим механизмом синхронизации. Это приводит к гонке данных (race condition), когда несколько горутин одновременно изменяют значение `counter`. В результате, фактическое значение `counter` по завершении всех горутин может быть меньше 100000, так как некоторые инкременты могут "теряться" из-за одновременного доступа к памяти без должной синхронизации.

Чтобы увидеть, какое значение будет у `counter` на вашем компьютере, вы можете запустить программу. Однако, без использования мьютекса или атомарных операций, результат будет недетерминированным и может изменяться при каждом запуске программы. Чтобы исправить эту проблему и гарантировать, что `counter` достигнет 100000, можно использовать `sync.Mutex` для защиты каждого инкремента или использовать `sync/atomic` для атомарного инкремента значения `counter`.

**Для исправления данной программы с использованием `sync.Mutex`** для корректной синхронизации доступа к переменной `counter`, можно добавить мьютекс, который будет блокировать доступ к переменной во время каждого инкремента. Вот исправленный вариант программы:

```go
package main

import (
	"fmt"
	"sync"
)

func main() {
	var counter int
	var wg sync.WaitGroup
	var mutex sync.Mutex // Добавляем мьютекс для синхронизации доступа к counter

	wg.Add(1000)

	for i := 0; i < 1000; i++ {
		go func() {
			for j := 0; j < 100; j++ {
				mutex.Lock() // Блокируем мьютекс перед изменением counter
				counter++
				mutex.Unlock() // Разблокируем мьютекс после изменения counter
			}
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(counter) // Теперь должно всегда выводить 100000
}
```

В этом коде `mutex.Lock()` вызывается перед каждым инкрементом `counter`, а `mutex.Unlock()` — сразу после инкремента. Это гарантирует, что в каждый момент времени только одна горутина может изменять значение `counter`, что предотвращает гонку данных и обеспечивает корректное выполнение программы с ожидаемым результатом в 100000.

Для исправления программы с использованием пакета `sync/atomic` для атомарных операций с переменной `counter`, вы можете использовать функцию `atomic.AddInt32` или `atomic.AddInt64` в зависимости от типа вашей переменной `counter`. Эти функции обеспечивают атомарное увеличение значения переменной, что предотвращает гонку данных без необходимости блокировки мьютексом.

Вот пример исправленной программы с использованием `atomic.AddInt64`:

```go
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var counter int64 // Изменим тип переменной на int64 для использования с atomic
	var wg sync.WaitGroup

	wg.Add(1000)

	for i := 0; i < 1000; i++ {
		go func() {
			for j := 0; j < 100; j++ {
				atomic.AddInt64(&counter, 1) // Атомарно увеличиваем counter на 1
			}
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println(counter) // Теперь должно всегда выводить 100000
}
```

В этом коде:

* Тип переменной `counter` изменен на `int64`, так как функции из пакета `sync/atomic` требуют указания типа данных.
* Вместо использования мьютекса для блокировки и разблокировки доступа к переменной, используется функция `atomic.AddInt64`, которая атомарно увеличивает значение `counter` на 1. Это обеспечивает безопасное изменение значения переменной из нескольких горутин без гонки данных.

Использование атомарных операций обычно более эффективно по сравнению с блокировками мьютексом, особенно в высококонкурентных сценариях, так как они уменьшают накладные расходы на управление блокировками.

### 2) В один канал поступают числа, во второй канал мы должны отправлять результаты их возведения в квадрат по мере возникновения данных в первом канале. Результаты работы программы должны выводиться на экран.

Для решения этой задачи на языке программирования Go, мы можем использовать горутины и каналы. Ниже приведен пример программы, которая читает числа из одного канала, возводит их в квадрат, отправляет результат в другой канал, и выводит результаты на экран.

```go
package main

import (
	"fmt"
)

func main() {
	// Создаем канал для входных чисел и канал для результатов
	input := make(chan int)
	output := make(chan int)

	// Горутина для чтения чисел, возведения их в квадрат и отправки результатов
	go func() {
		for num := range input {
			output <- num * num
		}
		close(output)
	}()

	// Отправляем числа в канал input
	go func() {
		for _, num := range []int{2, 4, 6, 8, 10} {
			input <- num
		}
		close(input)
	}()

	// Читаем и выводим результаты из канала output
	for result := range output {
		fmt.Println(result)
	}
}
```

#### Объяснение кода:

1. **Каналы**: Создаются два канала, `input` для входных чисел и `output` для результатов их возведения в квадрат.
2. **Горутина для обработки чисел**: В первой горутине читаются числа из канала `input`, каждое число возводится в квадрат, и результат отправляется в канал `output`. После того как все числа обработаны и канал `input` закрыт, канал `output` также закрывается.
3. **Отправка чисел в канал `input`**: Во второй горутине числа отправляются в канал `input`. После отправки всех чисел канал `input` закрывается, что сигнализирует первой горутине о том, что все данные были переданы.
4. **Чтение и вывод результатов**: В основной горутине читаются результаты из канала `output` и выводятся на экран. Как только канал `output` закрывается, цикл завершается.

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

### 3) Что выведет данная программа в зависимости от версий Go?

```go
package main

import (
 "fmt"
 "sync"
)

func main() {
wg := new(sync.WaitGroup)


for i := 0; i < 10; i++ {
    wg.Add(1)

    go func() {
        fmt.Println(i)
        wg.Done()
    }()
}

wg.Wait()

} 
```

Программа, которую вы привели, создает 10 горутин, каждая из которых печатает значение переменной `i`. Однако, из-за особенностей замыканий в Go, результат выполнения программы может зависеть от версии Go и от того, как именно компилятор обрабатывает замыкания.

#### Проблема с замыканиями

В данном коде все горутины используют одну и ту же переменную `i`, которая изменяется в цикле. Это означает, что к моменту выполнения горутин значение `i` может быть уже изменено. В результате, все горутины могут напечатать одно и то же значение, которое будет равно последнему значению `i` в цикле (в данном случае, `10`).

#### Ожидаемое поведение

**Go 1.19 и ранее**

В версиях Go до 1.20 включительно, горутины могут напечатать одно и то же значение `i`, которое будет равно `10`, так как все горутины могут начать выполнение после завершения цикла.

**Go 1.20 и позже**

В Go 1.20 была введена оптимизация, которая может изменить поведение замыканий в горутинах. Однако, даже с этой оптимизацией, результат все равно может быть непредсказуемым, так как горутины могут начать выполнение в любой момент времени, и значение `i` может изменяться.

#### Пример вывода

**Go 1.19 и ранее**

```
10
10
10
10
10
10
10
10
10
10
```

**Go 1.20 и позже**

Вывод может быть более разнообразным, но все равно непредсказуемым:

```
3
0
1
2
7
8
9
5
4
6
```

#### Исправление кода

Чтобы гарантировать, что каждая горутина печатает свое значение `i`, нужно передать `i` как параметр в анонимную функцию:

```go
package main

import (
	"fmt"
	"sync"
)

func main() {
	wg := new(sync.WaitGroup)

	for i := 0; i < 10; i++ {
		wg.Add(1)

		go func(i int) {
			defer wg.Done()
			fmt.Println(i)
		}(i)
	}

	wg.Wait()
}
```

#### Ожидаемый вывод после исправления

Теперь каждая горутина будет печатать свое значение `i`, и вывод будет следующим:

```
0
1
2
3
4
5
6
7
8
9
```

#### Заключение

* В версиях Go до 1.20 включительно, все горутины могут напечатать одно и то же значение `i`, равное `10`.
* В Go 1.20 и позже, поведение может быть более разнообразным, но все равно непредсказуемым.
* Чтобы гарантировать корректный вывод, нужно передать `i` как параметр в анонимную функцию.

### 4) Что будет выведено и как исправить?

```go
package main

import (
"fmt"
"sync"
"time"
)

func main() {
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		time.Sleep(time.Second * 2)
		fmt.Println("1")
		wg.Done()
	}()

	wg.Wait()

	go func() {
		fmt.Println("2")
	}()

	wg.Wait()
	fmt.Println("3")

}
```

Давайте разберем, что произойдет при выполнении данного кода, и как его можно исправить.

#### Описание кода

1. Создается переменная `wg` типа `sync.WaitGroup`.
2. Вызов `wg.Add(1)` увеличивает счетчик `WaitGroup` на 1.
3. Запускается первая горутина, которая спит 2 секунды, затем печатает "1" и вызывает `wg.Done()`, уменьшая счетчик `WaitGroup` на 1.
4. `wg.Wait()` блокирует выполнение основной горутины до тех пор, пока счетчик `WaitGroup` не станет равным 0.
5. После завершения первой горутины запускается вторая горутина, которая печатает "2".
6. Второй вызов `wg.Wait()` не блокирует выполнение, так как счетчик `WaitGroup` уже равен 0.
7. Печатается "3".

#### Проблема

Вторая горутина может завершиться до того, как будет напечатано "3", так как второй вызов `wg.Wait()` не блокирует выполнение. Это может привести к тому, что "2" может не быть напечатано до завершения программы.

#### Ожидаемый вывод

С большой вероятностью вывод будет следующим:

```
1
3
```

Однако, "2" может быть напечатано, если вторая горутина успеет выполниться до завершения программы:

```
1
2
3
```

#### Исправление

Чтобы гарантировать, что все горутины завершатся до завершения программы, нужно использовать `sync.WaitGroup` для всех горутин. Вот исправленный код:

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(time.Second * 2)
		fmt.Println("1")
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("2")
	}()

	wg.Wait()
	fmt.Println("3")
}
```

#### Объяснение исправленного кода

1. Добавляем еще один вызов `wg.Add(1)` перед запуском второй горутины.
2. Внутри второй горутины вызываем `defer wg.Done()`, чтобы уменьшить счетчик `WaitGroup` после завершения горутины.
3. Вызов `wg.Wait()` в конце основной функции гарантирует, что программа не завершится, пока обе горутины не завершат выполнение.

#### Ожидаемый вывод после исправления

Теперь вывод будет гарантированно следующим:

```
1
2
3
```

#### Заключение

Исправленный код гарантирует, что все горутины завершатся до завершения программы, и вывод будет предсказуемым.

### 5) Что здесь не так ? Какие есть способы решения ?

```go
package main

import (
	"fmt"
	"time"
)

var m map[string]int

func main() {
	m = make(map[string]int)
	go f1()
	go f2()

	time.Sleep(time.Second * 1)
	fmt.Printf("print map = %+v", m)

}

func f1() {
	for i := 0; i < 100000; i++ {
		m["f1"]++
	}
}

func f2() {
	for i := 0; i < 100000; i++ {
		m["f2"]++
	}
}

```

В данном коде есть проблема с конкурентным доступом к общей переменной `m` (карте). Две горутины (`f1` и `f2`) одновременно изменяют карту, что может привести к гонкам данных (data races) и непредсказуемому поведению программы.

#### Проблема

Гонки данных возникают, когда несколько горутин одновременно читают и записывают в одну и ту же переменную без надлежащей синхронизации. В данном случае, карта `m` изменяется одновременно из двух горутин, что может привести к некорректным результатам или даже к панике.

#### Способы решения

1. **Использование мьютекса (`sync.Mutex`)**:
   * Мьютекс (mutual exclusion) позволяет синхронизировать доступ к общей переменной, гарантируя, что только одна горутина может изменять переменную в любой момент времени.
2. **Использование синхронизированной карты (`sync.Map`)**:
   * Пакет `sync` предоставляет тип `sync.Map`, который является безопасным для использования в многопоточной среде.

#### Решение с использованием мьютекса

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	m  map[string]int
	mu sync.Mutex
)

func main() {
	m = make(map[string]int)
	go f1()
	go f2()

	time.Sleep(time.Second * 1)
	mu.Lock()
	fmt.Printf("print map = %+v", m)
	mu.Unlock()
}

func f1() {
	for i := 0; i < 100000; i++ {
		mu.Lock()
		m["f1"]++
		mu.Unlock()
	}
}

func f2() {
	for i := 0; i < 100000; i++ {
		mu.Lock()
		m["f2"]++
		mu.Unlock()
	}
}
```

#### Решение с использованием `sync.Map`

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

var m sync.Map

func main() {
	go f1()
	go f2()

	time.Sleep(time.Second * 1)
	printMap()
}

func f1() {
	for i := 0; i < 100000; i++ {
		val, _ := m.LoadOrStore("f1", 0)
		m.Store("f1", val.(int)+1)
	}
}

func f2() {
	for i := 0; i < 100000; i++ {
		val, _ := m.LoadOrStore("f2", 0)
		m.Store("f2", val.(int)+1)
	}
}

func printMap() {
	m.Range(func(key, value interface{}) bool {
		fmt.Printf("%s: %d\n", key, value)
		return true
	})
}
```

#### Объяснение решений

1. **Использование мьютекса**:
   * В этом решении используется мьютекс `mu` для синхронизации доступа к карте `m`. Каждый раз, когда горутина хочет изменить карту, она захватывает мьютекс с помощью `mu.Lock()`, выполняет изменения и затем освобождает мьютекс с помощью `mu.Unlock()`. Это гарантирует, что только одна горутина может изменять карту в любой момент времени.
2. **Использование `sync.Map`**:
   * В этом решении используется `sync.Map`, который является безопасным для использования в многопоточной среде. Методы `LoadOrStore` и `Store` обеспечивают атомарные операции для чтения и записи значений. Метод `Range` используется для итерации по карте и печати значений.

#### Заключение

Оба решения обеспечивают корректный и безопасный доступ к общей карте в многопоточной среде. Выбор между ними зависит от конкретных требований и предпочтений. Мьютексы предоставляют более явный контроль над синхронизацией, в то время как `sync.Map` упрощает работу с картами в многопоточной среде.

### 6) Что выведет данная программа и почему?

```go
package main

import(
	"time"
)

func main() {
	timeStart := time.Now()
	_, _ = <-worker(), <-worker()
	
	println(int(time.Since(timeStart).Seconds()))	
}
	
func worker() chan int {
	ch := make(chan int)
	go func() {
		time.Sleep(3 * time.Second)
		ch <- 3
	}()
	return ch
}
```

#### Разбор кода

1. **Функция `main`**:
   * Записывает текущее время в переменную `timeStart`.
   * Запускает две горутины через вызов функции `worker` и ожидает получения значений из двух каналов.
   * Выводит количество секунд, прошедших с момента запуска программы.
2. **Функция `worker`**:
   * Создает канал `ch`.
   * Запускает горутину, которая ждет 3 секунды и затем отправляет значение `3` в канал `ch`.
   * Возвращает канал `ch`.

#### Что происходит при выполнении программы

* В `main` вызываются две функции `worker`, каждая из которых запускает горутину, которая ждет 3 секунды и затем отправляет значение в канал.
* Оператор `_, _ = <-worker(), <-worker()` ожидает получения значений из обоих каналов.
* Важно отметить, что оператор `_, _ = <-worker(), <-worker()` выполняет последовательное ожидание значений из каналов, а не параллельное.

#### Почему программа выводит `6`

* Первая горутина, запущенная функцией `worker`, ждет 3 секунды перед отправкой значения в канал.
* Оператор `_, _ = <-worker(), <-worker()` сначала ожидает получения значения из первого канала, что занимает 3 секунды.
* После получения значения из первого канала, оператор переходит к ожиданию значения из второго канала, что занимает еще 3 секунды.
* В результате общее время ожидания составляет 3 + 3 = 6 секунд.

#### Исправление для параллельного ожидания

Чтобы ожидание значений из каналов происходило параллельно, можно использовать `sync.WaitGroup`:

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	timeStart := time.Now()
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		<-worker()
	}()

	go func() {
		defer wg.Done()
		<-worker()
	}()

	wg.Wait()
	fmt.Println(int(time.Since(timeStart).Seconds()))
}

func worker() chan int {
	ch := make(chan int)
	go func() {
		time.Sleep(3 * time.Second)
		ch <- 3
	}()
	return ch
}
```

#### Вывод исправленной программы

Исправленная программа выведет `3`, так как обе горутины будут выполняться параллельно, и общее время ожидания составит 3 секунды.

#### Заключение

Изначальная программа выводит `6`, потому что оператор `_, _ = <-worker(), <-worker()` выполняет последовательное ожидание значений из каналов, что приводит к суммарному времени ожидания в 6 секунд. Для параллельного ожидания значений из каналов можно использовать `sync.WaitGroup`.

### 7) Что выведет данный код и почему?

<pre class="language-go"><code class="lang-go">package main

import "fmt"

func main() {
    for i := 0; i &#x3C; 5; i++ {
<strong>    go func() {
</strong>        fmt.Println(i)
    }()
    }
}
</code></pre>

Этот код создает пять горутин, каждая из которых печатает значение переменной `i`. Однако, из-за того, что горутины выполняются асинхронно, значение `i` может измениться до того, как горутина выполнится. Это приводит к неопределенному поведению, и все горутины могут напечатать одно и то же значение `i`, которое будет равно 5 (последнее значение в цикле).

#### Проблема

Горутины захватывают переменную `i` по ссылке, и к моменту выполнения горутины значение `i` может измениться. В результате все горутины могут напечатать одно и то же значение.

#### Поведение в разных версиях Go

В разных версиях Go поведение этого кода может быть одинаковым, так как проблема заключается в захвате переменной `i` по ссылке, а не в самой реализации языка. Однако, в зависимости от времени выполнения горутин, результаты могут быть разными.

1. **Go 1.4 и ранее**:
   * В этих версиях Go горутины могут захватывать переменную `i` по ссылке, что приводит к тому, что все горутины могут напечатать одно и то же значение `i`, которое будет равно 5.
2. **Go 1.5 и позже**:
   * В этих версиях Go поведение остается таким же, так как проблема захвата переменной по ссылке не изменилась. Все горутины могут напечатать значение `i`, равное 5.

#### Пример вывода

В большинстве случаев, независимо от версии Go, вы увидите следующий вывод:

```
5
5
5
5
5
```

#### Исправление кода

Чтобы избежать этой проблемы, можно передать текущее значение `i` в качестве аргумента в анонимную функцию. Это создаст новую копию переменной `i` для каждой горутины.

#### Исправленный код

```go
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			fmt.Println(i)
		}(i)
	}

	wg.Wait()
}
```

#### Объяснение исправлений

1. **Передача `i` как аргумента**:
   * В анонимную функцию передается текущее значение `i` как аргумент. Это создает новую копию переменной `i` для каждой горутины, что предотвращает проблему захвата переменной по ссылке.
2. **Использование `sync.WaitGroup`**:
   * Для того чтобы дождаться завершения всех горутин перед завершением программы, используется `sync.WaitGroup`.
   * `wg.Add(1)` увеличивает счетчик горутин, которые нужно дождаться.
   * `defer wg.Done()` уменьшает счетчик на 1 после завершения работы горутины.
   * `wg.Wait()` блокирует выполнение функции `main` до тех пор, пока счетчик не станет равным нулю, то есть пока все горутины не завершат свою работу.

#### Заключение

Эти изменения гарантируют, что каждая горутина будет работать с правильным значением переменной `i`, и программа дождется завершения всех горутин перед завершением. Это делает код более предсказуемым и корректным.
