Практика: Конкурентность в Go
Задача 1: Параллельная обработка данных
Создайте программу, которая параллельно обрабатывает массив URL и собирает статусы ответов.
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
// Task: Реализуйте функцию checkURLs
func checkURLs(urls []string) map[string]string {
// Ваш код здесь
// Подсказки:
// 1. Создайте WaitGroup для ожидания всех горутин
// 2. Используйте мьютекс для защиты мапы результатов
// 3. Запустите горутину для каждого URL
// 4. Сделайте HTTP GET запрос с тайм-аутом
return nil
}
func main() {
urls := []string{
"https://google.com",
"https://github.com",
"https://nonexistent.example.com",
"https://httpbin.org/status/404",
}
start := time.Now()
results := checkURLs(urls)
elapsed := time.Since(start)
fmt.Printf("Время выполнения: %v\n", elapsed)
fmt.Println("Результаты:")
for url, status := range results {
fmt.Printf(" %s: %s\n", url, status)
}
}
Ожидаемый вывод:
Время выполнения: ~1-2 секунды (параллельно)
Результаты:
https://google.com: 200 OK
https://github.com: 200 OK
https://nonexistent.example.com: error
https://httpbin.org/status/404: 404 Not Found
Задача 2: Конвейер (Pipeline)
Реализуйте классическую схему конвейера: числа → квадраты → фильтр чётных → сумма.
package main
import (
"fmt"
"sync"
)
// Task: Реализуйте pipeline
func pipeline(numbers []int) int {
// Ваш код здесь
// Подсказки:
// 1. Создайте каналы для каждого этапа
// 2. Первый этап: отправка чисел в канал
// 3. Второй этап: возведение в квадрат
// 4. Третий этап: фильтрация чётных
// 5. Четвёртый этап: суммирование
// 6. Используйте close() для сигнала завершения
return 0
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := pipeline(numbers)
fmt.Printf("Результат: %d\n", result)
// 2² + 4² + 6² + 8² + 10² = 4 + 16 + 36 + 64 + 100 = 220
}
Задача 3: Пул воркеров
Создайте пул горутин-воркеров для обработки задач из очереди.
package main
import (
"fmt"
"sync"
"time"
)
type Task struct {
ID int
Duration time.Duration
}
func worker(id int, tasks <-chan Task, wg *sync.WaitGroup) {
defer wg.Done()
for task := range tasks {
fmt.Printf("Worker %d: начал задачу %d (время: %v)\n", id, task.ID, task.Duration)
time.Sleep(task.Duration)
fmt.Printf("Worker %d: завершил задачу %d\n", id, task.ID)
}
fmt.Printf("Worker %d: вышел\n", id)
}
// Task: Создайте пул воркеров
func processTasks(tasks []Task, numWorkers int) {
// Ваш код здесь
// Подсказки:
// 1. Создайте буферизированный канал для задач
// 2. Запустите numWorkers воркеров
// 3. Отправьте все задачи в канал
// 4. Закройте канал после отправки всех задач
// 5. Дождитесь завершения всех воркеров
}
func main() {
tasks := []Task{
{ID: 1, Duration: 100 * time.Millisecond},
{ID: 2, Duration: 200 * time.Millisecond},
{ID: 3, Duration: 150 * time.Millisecond},
{ID: 4, Duration: 300 * time.Millisecond},
{ID: 5, Duration: 100 * time.Millisecond},
{ID: 6, Duration: 250 * time.Millisecond},
{ID: 7, Duration: 175 * time.Millisecond},
{ID: 8, Duration: 125 * time.Millisecond},
}
start := time.Now()
processTasks(tasks, 3)
elapsed := time.Since(start)
fmt.Printf("Общее время: %v\n", elapsed)
// При 3 воркерах время должно быть ~800-900ms
}
Задача 4: Обедающие философы
Реализуйте задачу "Обедающие философы" с использованием каналов или mutex.
package main
import (
"fmt"
"sync"
"time"
)
type Philosopher struct {
id int
leftFork int
rightFork int
}
// Task: Реализуйте обед философов
func diningPhilosophers(num int) {
// Ваш код здесь
// Подсказки:
// 1. Создайте вилки (мьютексы или каналы)
// 2. Каждый философ пытается взять левую, затем правую вилку
// 3. После еды освобождает обе вилки
// 4. Избегайте deadlock (например, последний философ берёт правую первой)
}
func main() {
numPhilosophers := 5
fmt.Printf("Обед %d философов начался...\n", numPhilosophers)
start := time.Now()
diningPhilosophers(numPhilosophers)
elapsed := time.Since(start)
fmt.Printf("Обед завершён за %v\n", elapsed)
}
Задача 5: Реализация rate limiter
Создайте rate limiter с использованием каналов.
package main
import (
"fmt"
"time"
)
// Task: Создайте rate limiter
type RateLimiter struct {
// Ваши поля
}
func NewRateLimiter(requestsPerSecond int) *RateLimiter {
// Создайте rate limiter
return nil
}
func (rl *RateLimiter) Allow() bool {
// Проверьте, можно ли отправить запрос
return false
}
func main() {
limiter := NewRateLimiter(5) // 5 запросов в секунду
// Тест: делаем 15 запросов
for i := 1; i <= 15; i++ {
allowed := limiter.Allow()
fmt.Printf("Запрос %d: %v\n", i, allowed)
time.Sleep(200 * time.Millisecond)
}
}
Решения
Решение задачи 1
func checkURLs(urls []string) map[string]string {
results := make(map[string]string)
var mu sync.Mutex
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
client := http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Get(u)
status := "error"
if err == nil {
status = fmt.Sprintf("%d %s", resp.StatusCode, resp.Status)
resp.Body.Close()
}
mu.Lock()
results[u] = status
mu.Unlock()
}(url)
}
wg.Wait()
return results
}
Решение задачи 2
func pipeline(numbers []int) int {
// Каналы для этапов
genCh := make(chan int, len(numbers))
squareCh := make(chan int, len(numbers))
filterCh := make(chan int, len(numbers))
// Этап 1: Генерация
go func() {
for _, n := range numbers {
genCh <- n
}
close(genCh)
}()
// Этап 2: Квадраты
go func() {
for n := range genCh {
squareCh <- n * n
}
close(squareCh)
}()
// Этап 3: Фильтр чётных
go func() {
for n := range squareCh {
if n%2 == 0 {
filterCh <- n
}
}
close(filterCh)
}()
// Этап 4: Суммирование
sum := 0
for n := range filterCh {
sum += n
}
return sum
}
Решение задачи 3
func processTasks(tasks []Task, numWorkers int) {
tasksCh := make(chan Task, len(tasks))
var wg sync.WaitGroup
// Запускаем воркеры
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, tasksCh, &wg)
}
// Отправляем задачи
for _, task := range tasks {
tasksCh <- task
}
close(tasksCh)
// Ждём завершения
wg.Wait()
}
Решение задачи 4
func diningPhilosophers(num int) {
forks := make([]sync.Mutex, num)
eat := func(id, left, right int) {
fmt.Printf("Philosopher %d started eating\n", id)
time.Sleep(100 * time.Millisecond)
fmt.Printf("Philosopher %d finished eating\n", id)
}
var wg sync.WaitGroup
wg.Add(num)
for i := 0; i < num; i++ {
go func(id int) {
defer wg.Done()
left := id
right := (id + 1) % num
// Избегаем deadlock: последний философ берёт вилки в обратном порядке
if id == num-1 {
forks[right].Lock()
forks[left].Lock()
} else {
forks[left].Lock()
forks[right].Lock()
}
eat(id, left, right)
forks[left].Unlock()
forks[right].Unlock()
}(i)
}
wg.Wait()
}
Решение задачи 5
type RateLimiter struct {
tokens chan struct{}
}
func NewRateLimiter(requestsPerSecond int) *RateLimiter {
rl := &RateLimiter{
tokens: make(chan struct{}, requestsPerSecond),
}
// Заполняем токены
for i := 0; i < requestsPerSecond; i++ {
rl.tokens <- struct{}{}
}
// Пополняем токены каждую секунду
go func() {
ticker := time.NewTicker(time.Second / time.Duration(requestsPerSecond))
for range ticker.C {
select {
case rl.tokens <- struct{}{}:
default:
}
}
}()
return rl
}
func (rl *RateLimiter) Allow() bool {
select {
case <-rl.tokens:
return true
default:
return false
}
}