Tekhnichna spivbesida z Go: Goroutines, Channels ta Concurrency
Pytannia na spivbesidi z Go shchodo goroutines, channels ta paterniw konkurentnosti. Pryklady kodu, typowi pastky ta ekspertni widpovidi dlia pidhotovky do tekhnichnykh spivbesid z Go u 2026 rotsi.

Pytannia pro goroutines, channels ta konkurentnist u Go stabilno zalyshaiutsia sered naiskladnishykh tem, z iakymy stykaiutsia kandydaty na spivbesidakh. Hlyboke rozuminnia tsykh kontseptsii widrizniaje seniornykh inzheneriv Go vid tykh, khto shche vyvchaje movu. Tsei posibnyk okhopliuje same ti pytannia, iaki rekrutery stavliat u 2026 rotsi, z vyrobnychymy prykladamy kodu ta obgruntuvanniam kozhnoji widpovidi.
Spivbesidy z konkurentnosti Go zoseredzheni na triokh oblastiakh: keruvannia zhyttievym tsyklom goroutines, semantyka kanaliw (buferowani vs nebuferowani, napriamkowi typy) ta kompozytsiia paterniv (fan-out/fan-in, puly workeriv, skasuvannia kontekstu). Zapamiatovuvannia syntaksysu nedostatnio — rekrutery ochikujut, shcho kandydaty zmozhut mirkuvaty pro perehony danykh ta vzajemni blokuvannia.
Osnovy Goroutines: Shcho pytaje kozhen rekruter
Pershyi raund pytan zazwychaj perevirjaje, chy kandydat rozumije, chym goroutines nasrawdi je — a ne lyshe jak jikh zapuskaty.
P: Shcho take goroutine i chym win widrizniajetsia wid potoku operatsijnoji systemy?
Goroutine — tse lehka konkurentna funktsiia, iakoiu keruje planuwalnyk runtime Go, a ne operatsijna systema. Runtime Go multypleksuje tysiachi goroutines na newelyku kilkist potokiw OS za dopomohoiu modeli planuvannia M:N (M goroutines widobrazhajutsia na N potokiw OS).
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
// Print the number of OS threads available
fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Each goroutine starts with ~2-8KB stack
// OS threads typically start with 1-8MB
_ = id
}(i)
}
wg.Wait()
fmt.Println("All 10,000 goroutines completed")
}Kliuchowi widminnosti, iaki slid zhadaty na spivbesidi: goroutines startujut zi stekom 2-8KB, iakyi dynamichno zrostaje, na widminu wid fiksowoho steku 1-8MB potoku OS. Peremykannia kontekstu mizh goroutines widbuwajetsja u prostorowij korystuwacha planuwalnikom Go, shcho unykaje koshtovnoho peremykannia na riwni jadra systemy. Tse robyt zapusk 100 000 goroutines praktychnym, todi jak 100 000 potokiw OS vycherpaly b systemni resursy.
P: Shcho widbuwajetsja, koly goroutine vyklyche panic?
Neobroblenyi panic u bud-iakomu goroutine sprychnjiaje crash usijeji prohramy. Na widminu wid vykliuchenʼ u Java chy Python, panic poshyrjujetsia whoru po steku wyklykiw swogo goroutine — a ne steku goroutine, iakyi joho zapustyv. Jedynym sposobom perkhopyty tse je recover() u seredyni widkladenoji funktsiji w tomu zh goroutine.
package main
import "fmt"
func safeGo(fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from panic:", r)
}
}()
fn() // execute the actual work
}()
}
func main() {
safeGo(func() {
panic("something went wrong")
})
// Give goroutine time to complete
select {}
}Rekrutery shukajut uswidomlennia toho, shcho vyrobnychi servisy Go obhortajut zapusky goroutines u patern widnowlennia. Biblioteky na kshtalt errgroup obrobliajut tse bilsh elehantno.
Semantyka Kanaliw: Buferowani, Nebuferowani ta Napriamkowi
Pytannia pro kanaly wyjavliajut, chy kandydat dijsno rozumije model konkurentnosti Go, chy prosto zapamiatovuje paterny.
P: Iaka riznytsia mizh buferowanym i nebuferowanym kanalom?
Nebuferowanyi kanal (make(chan T)) vymaghaje, shchob widprawnik i otrymuvach buly hotowi odnochasno — widsylannia blokuje, doky inshyi goroutine ne otrymaje znachennia. Buferowanyi kanal (make(chan T, n)) dozwoliaje widisylaty do n znachen bez blokuvannia.
package main
import "fmt"
func main() {
// Unbuffered: send blocks until receive is ready
ch := make(chan string)
go func() {
ch <- "hello" // blocks here until main reads
}()
msg := <-ch
fmt.Println(msg)
// Buffered: send does not block until buffer is full
buf := make(chan int, 3)
buf <- 1 // does not block (buffer has space)
buf <- 2 // does not block
buf <- 3 // does not block
// buf <- 4 would block — buffer is full
fmt.Println(<-buf, <-buf, <-buf) // 1 2 3
}Chastu dodatkowe pytannia: "Koly wybraty odne abo inshe?" Nebuferowani kanaly zabezpechujut synkhronizatsiiu — korysni, koly widprawnik maje znaty, shcho otrymuvach obrobyv znachennia. Buferowani kanaly rozdialiajut chas widprawnika ta otrymuwcha — korysni dlia cherh zaavdanh abo obmezhennia shwydkosti, de pewnyi zapas pryjniatnyi.
P: Shcho widbuwajetsja, koly kanal zakrywajetsja?
Zakryttia kanalu syghnalizuje, shcho bilshe znachen ne bude widsilano. Otrymuwannia z zakrytoho kanalu negajno povertaje nulowe znachennia. Widsylannia na zakrytyi kanal sprychniuje panic. Tsykl range po kanalu zawershajetsja, koly kanal zakrywajetsja.
package main
import "fmt"
func producer(ch chan<- int, count int) {
for i := 0; i < count; i++ {
ch <- i
}
close(ch) // signal: no more values
}
func main() {
ch := make(chan int, 5)
go producer(ch, 5)
// range exits automatically when channel closes
for val := range ch {
fmt.Println("received:", val)
}
// Reading from closed channel returns zero value + false
val, ok := <-ch
fmt.Printf("after close: val=%d, ok=%v\n", val, ok)
}Krytychnyi moment: kanal maje zakrywaty lyshe widprawnik, nikoly otrymuvach. Zakryttia kanalu, w iakyi inshyi goroutine shche pyshe, sprychnjiuje panic.
Готовий до співбесід з Go?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Instruktsija Select: Multypleksuvannia Kanaliw
P: Iak pratsiuje select i shcho widbuwajetsja, koly kilka wypadkiw hotowi odnochasno?
Instruktsija select blokuje, doky odna z operatsij na kanali ne zmozhe buty wykonana. Koly kilka wypadkiw hotowi odnochasno, Go obyraje odyn wypadkovo — tse zapobihaje holoduwanniu bud-iakoho okremogo wypadku.
package main
import (
"context"
"fmt"
"time"
)
func fetchFromAPI(ctx context.Context, url string) (string, error) {
resultCh := make(chan string, 1)
errCh := make(chan error, 1)
go func() {
// Simulate API call
time.Sleep(200 * time.Millisecond)
resultCh <- fmt.Sprintf("data from %s", url)
}()
select {
case result := <-resultCh:
return result, nil
case err := <-errCh:
return "", err
case <-ctx.Done():
// Context cancelled or timed out
return "", ctx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
result, err := fetchFromAPI(ctx, "https://api.example.com/data")
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(result)
}Rekrutery testujut dvi rechi za dopomohoiu select: rozuminnia prawyla wypadkowoho wyboru ta zdatnist pojednuwaty kanaly z context.Context dlia paterniv tajm-autiw ta skasuvannia.
Populiarni paterny konkurentnosti na spivbesidakh
Spivbesidy seniornoho riwnia z Go maizhe zawzhdy wkliuchajut pytannia pro implementatsiiu odnoho z tsykh paterniv z nulia.
Patern Fan-Out/Fan-In
P: Implementujte pipeline fan-out/fan-in, iakyi obrobliaje elementy konkurentno.
Fan-out rozpodijiaje robotu mizh kilkoma goroutines. Fan-in zbyraje rezultaty z kilkokh goroutines u odyn kanal.
package main
import (
"fmt"
"sync"
)
// generator produces values on a channel
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// square reads from input, squares each value
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
// fanIn merges multiple channels into one
func fanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
merged := make(chan int)
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for val := range c {
merged <- val
}
}(ch)
}
go func() {
wg.Wait()
close(merged) // close after all inputs are drained
}()
return merged
}
func main() {
in := generator(2, 3, 4, 5, 6)
// Fan out: two goroutines reading from same channel
c1 := square(in)
c2 := square(in)
// Fan in: merge results
for result := range fanIn(c1, c2) {
fmt.Println(result)
}
}Kliuchowyi wysnowok, iakoho ochikujut rekrutery: kanal generator spilnyi dlia c1 i c2, tozh kozhne znachennia obrobliajetsja rivno odnym workerom (ne dybljujetsia). Funktsiia fanIn wykorystowuje WaitGroup, shchob znaty, koly wsi wkhidni kanaly sporizhneni, pered zakryttiam objedanoho kanalu.
Pul Workeriv z errgroup
P: Iak implementuwaty obmezhenyj pul workeriv z obrobkoiu pomylok?
Paket golang.org/x/sync/errgroup (chastyna rozshyrenoji standartnoji biblioteky Go) rozviazuje tse chysto. Win keruje zhyttievym tsyklom goroutines, zbyraje pershu pomylku ta integrujetsja z context dlia skasuvannia.
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func processItem(ctx context.Context, id int) error {
// Check for cancellation before heavy work
select {
case <-ctx.Done():
return ctx.Err()
default:
}
if id == 7 {
return fmt.Errorf("failed to process item %d", id)
}
fmt.Printf("processed item %d\n", id)
return nil
}
func main() {
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(3) // maximum 3 concurrent goroutines
for i := 0; i < 10; i++ {
id := i
g.Go(func() error {
return processItem(ctx, id)
})
}
// Wait blocks until all goroutines finish
// Returns the first non-nil error
if err := g.Wait(); err != nil {
fmt.Println("pipeline error:", err)
}
}Stanom na Go 1.24 (potochna stabilna wersiia na pochatok 2026 roku) tsei patern zalyshajetsja rekomendowanym pidkhodom. Metod SetLimit buv dodanyi u Go 1.20 i usuwaje potrebu w ruchnij implementatsiji obmezhennia konkurentnosti na osnowi semaforiv.
Perehony danykh ta prymitywy sync
P: Iak wyjavliaty ta zapobihaty perehony danykh u Go?
Go nadaje wbudowanyi detektor perehoniw, iakyi aktywujetsja flazhkom -race. Win wyjavliaje nesynkhronizowanyi odnochasnyi dostup do spilnoji pamiati pid chas wykonannia.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
// BAD: race condition — do not use in production
func unsafeCounter() int {
counter := 0
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter++ // DATA RACE: concurrent read/write
}()
}
wg.Wait()
return counter // result is non-deterministic
}
// GOOD: atomic operations for simple counters
func atomicCounter() int64 {
var counter atomic.Int64
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Add(1) // thread-safe atomic increment
}()
}
wg.Wait()
return counter.Load() // always 1000
}
// GOOD: mutex for complex shared state
type SafeMap struct {
mu sync.RWMutex
data map[string]int
}
func (m *SafeMap) Set(key string, val int) {
m.mu.Lock() // exclusive lock for writes
defer m.mu.Unlock()
m.data[key] = val
}
func (m *SafeMap) Get(key string) (int, bool) {
m.mu.RLock() // shared lock for reads
defer m.mu.RUnlock()
v, ok := m.data[key]
return v, ok
}
func main() {
fmt.Println("unsafe:", unsafeCounter()) // unpredictable
fmt.Println("atomic:", atomicCounter()) // always 1000
}Widpowid na spivbesidi maje okhopliuvaty try stratehiji synkhronizatsiji: sync.Mutex / sync.RWMutex dlia skladnoho spilnoho stanu, sync/atomic dlia prostykh lichylnykiw ta flazhkiw, ta kanaly dlia komunikatsiji mizh goroutines ("dilitsia pamiattiiu cherez komunikatsiiu, a ne komunikuwaty cherez spilnu pamiat"). Zapusk go test -race ./... maje buty chastynoju kozhnoho CI pipeline.
Kontekst ta paterny skasuvannia
P: Iak context.Context keruje zhyttievym tsyklom goroutines?
Paket context nadaje mekhanizm poshyrennia syhnaliw skasuvannia, dedlajniw ta znachen oblasti zapytu cherez mezhi goroutines. Kozhna dowhotrywala goroutine powynna pryjmaty context.Context iak swij pershyi parametr.
package main
import (
"context"
"fmt"
"time"
)
// worker simulates a long-running task
func worker(ctx context.Context, id int, results chan<- string) {
select {
case <-time.After(time.Duration(id*100) * time.Millisecond):
results <- fmt.Sprintf("worker %d: done", id)
case <-ctx.Done():
results <- fmt.Sprintf("worker %d: cancelled (%v)", id, ctx.Err())
}
}
func main() {
// Parent context with 250ms deadline
ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
defer cancel()
results := make(chan string, 5)
// Launch 5 workers with increasing durations
for i := 1; i <= 5; i++ {
go worker(ctx, i, results)
}
// Collect all results
for i := 0; i < 5; i++ {
fmt.Println(<-results)
}
}Workery 1 i 2 zawershujutsia w mezhakh dedlajnu 250ms. Workery 3, 4 i 5 otrymujut syhnal skasuvannia cherez ctx.Done(). Tsei patern je fundamentalnym dlia pobudowy stijkykh HTTP-serweriv ta mikroservisiv u Go — kozhen obrobnik zapytiv otrymuje kontekst, iakyi poshyrjuje skasuvannia, koly klijent widkliuchajetsja.
Nikoly ne slid zberihaty context.Context u poli struktury. Ofitsijna dokumentatsiia Go chisko stverdzuje: "Ne zberihaite konteksty u seredyni typiw struktur; natomist peredavaite kontekst jawno kozhnij funktsiji, iaka joho potrebuje." Rekrutery testujut tse, shchob otsinty, chy dotrymujetsia kandydat konwentsij Go.
Vyjavlennia vzajemnykh blokuwan: Pidstupni pytannia na spivbesidi
P: Chy tsei kod sprychnyt vzajemne blokuvannia? Chomu?
Pytannia pro vzajemni blokuvannia populiarni, bo testujut zdatnist kandydata mirkuwaty pro planuvannia goroutines ta operatsiji z kanalamy.
package main
func main() {
ch := make(chan int)
ch <- 42 // DEADLOCK: unbuffered send with no receiver
// The main goroutine blocks here forever
// Go runtime detects this: "fatal error: all goroutines are asleep"
}Vyrishhennia proste: abo zrobyty kanal buferowanym (make(chan int, 1)), abo zapustyty goroutine dlia otrymuwannia pered widsylanniam. Runtime Go wyjavliaje vzajemni blokuvannia, koly wsi goroutines zablokwani — ale lyshe koly wsi goroutines spliat. Yakshcho khoch odna goroutine pratsiuje (napryklad, fonovyi HTTP-serwer), runtime ne vyjavyt chastkowoho blokuvannia.
Runtime Go wyjavliaje vzajemni blokuvannia lyshe koly kozhna goroutine u prohrami zablokowana. U realnykh zastosunkakh z HTTP-serveramy abo fonovymy workeramy, vytikli goroutines u stani blokuvannia ne zapustiut detektor runtime. Instrumenty na kshtalt pprof ta dampy goroutines (runtime.Stack) neobkhidni dlia diahnostyky tsykh problem na prodaksheni.
Prosunutyj patern: Konkurentna obrobka z obmezhenniam shwydkosti
P: Iak implementuwaty konkurentni API-wyklyky z obmezhenniam shwydkosti?
Tse pytannia testuje zdatnist pojednuwaty kilka prymitywiw konkurentnosti u tsilisnye rishennia.
package main
import (
"context"
"fmt"
"sync"
"time"
)
// RateLimiter controls concurrent and temporal access
type RateLimiter struct {
semaphore chan struct{} // limits concurrency
ticker *time.Ticker // limits rate
}
func NewRateLimiter(maxConcurrent int, interval time.Duration) *RateLimiter {
return &RateLimiter{
semaphore: make(chan struct{}, maxConcurrent),
ticker: time.NewTicker(interval),
}
}
func (rl *RateLimiter) Execute(ctx context.Context, fn func() error) error {
// Wait for rate limit tick
select {
case <-rl.ticker.C:
case <-ctx.Done():
return ctx.Err()
}
// Acquire concurrency slot
select {
case rl.semaphore <- struct{}{}:
case <-ctx.Done():
return ctx.Err()
}
defer func() { <-rl.semaphore }() // release slot
return fn()
}
func main() {
rl := NewRateLimiter(3, 100*time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
err := rl.Execute(ctx, func() error {
fmt.Printf("[%v] processing %d\n", time.Now().Format("04:05.000"), id)
time.Sleep(150 * time.Millisecond) // simulate work
return nil
})
if err != nil {
fmt.Printf("item %d: %v\n", id, err)
}
}(i)
}
wg.Wait()
}Tsei patern pojednuje kanalnyj semafor (dlia obmezhennia konkurentnosti) z tikerom (dlia obmezhennia shwydkosti). Podwijnyj select z perevirkoiu kontekstu zabezpechuje graceful shutdown. Same takyi wyrobnychyj riwen widpovidi wyriznijaje seniornykh kandydatiw.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Wysnovok
- Goroutines — tse potoki u prostori korystuwacha, iakymy keruje runtime Go z planuwanniam M:N; zawzhdy slid obrobliaty panic u zapushchenykh goroutines
- Nebuferowani kanaly synkhronizujut widprawnika i otrymuwcha; buferowani kanaly rozdialiajut chas — wibir zalezhyt wid toho, chy widprawnik potrebuje pidtwerdzhhennia
- Instruktsija
selectmultypleksuje operatsiji z kanalamy z wypadkowym wyborom, koly kilka hotowi; pojednujetsia zcontext.Contextdlia tajm-autiw - Fan-out/fan-in ta puly workeriv (cherez
errgroup.SetLimit) — dwa paterniv konkurentnosti, pro iaki pytajut najchastishe sync.Mutexdlia skladnoho spilnoho stanu,sync/atomicdlia prostykh lichylnykiw, kanaly dlia komunikatsiji mizh goroutines- Zawzhdy slid zapuskaty
go test -raceu CI dlia wyjavlennia perehoniw danykh; chastkowi vzajemni blokuvannia potrebujutpprofdlia diahnostyky - Nikoly ne slid zberihaty
context.Contextu strukturakh — slid peredawaty joho iak pershyi parametr funktsiji - Obmezhennia shwydkosti u Go pojednuje kanalni semafory z tikeramy, obhornutymy u instruktsiji select, shcho wrakhovujut kontekst
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

Конкурентність у Go: Горутини та Канали - Повний Посібник
Опануйте конкурентність у Go з горутинами та каналами. Просунуті патерни, синхронізація, інструкції select та найкращі практики з докладними прикладами коду.

Топ 25 запитань на співбесіду з Go: повний посібник для розробника
Підкорюйте співбесіди з Go завдяки 25 найпоширенішим запитанням. Горутини, канали, інтерфейси та патерни конкурентності з прикладами коду.

Go: Основи для Java/Python-розробників у 2026 році
Швидке вивчення Go з використанням досвіду Java або Python. Goroutines, channels, інтерфейси та основні патерни для плавного переходу.