Этот пост посвящен goroutine, mutex и waitgroup. Если вы не смотрели базовую часть - часть 1, рекомендуем сначала ознакомиться с ней.
Основы goroutine, mutex и waitgroup в Go
Мы постарались сделать материал максимально простым и интерактивным - ниже приведен код, с которым можно поиграться и разобраться в концепциях за пару минут с помощью Go Playground.
Подход "обучение через практику" работает лучше всего!
package main
import (
"fmt"
"sort"
"strings"
"sync"
"unicode"
)
func main() {
// Below array of 5 strings are simulating 5 files.
// Now I want to read all 5 files in asynchronously in parallel manner with the help of
// GO Routine
// Task 1 - collect all words in a file and its frequency.
// Task 2 - Combine all results and then give top frequently occuring words.
files := []string{
"Go is a powerful language for concurrent programming tasks today.",
"Hello world in Go makes coding fun and efficient always.",
"Concurrent code with goroutines is what makes Go shine bright.",
"Programming in Go handles errors well and keeps things simple.",
"World of Go developers love its speed and reliability features.",
}
// Global frequency map and mutex for thread safety
// Variable declared in main or at file level are global variables in a file.
var freqMap = make(map[string]int) // Key-String , Count - int
var mu sync.Mutex
var wg sync.WaitGroup
fmt.Println("Main: Starting to process files...")
// Process every "file / String " concurrently - outer for loop
for i, content := range files {
wg.Add(1) // Count asychronous tasks . Add(1) for every Go routine
fmt.Printf("Main: Adding to WaitGroup and launching goroutine for file %d\n", i)
// nameless function for GO routine
go func(idx int, text string) {
// Watch the logs in console to understand the thread like behaviour of GO Routines
fmt.Printf("Goroutine %d: Started processing\n", idx)
defer wg.Done() // Similar to final block in Java called when function exists
// Local frequency map for every file created in every iteration
localFreq := make(map[string]int)
// Clean and split into words into "text" containing file text
words := strings.Fields(text)
fmt.Printf("Goroutine %d: Split text into %d words\n", idx, len(words))
// Inner for loop - processing individual words collected in a "words" array
for _, word := range words {
// Remove punctuation and convert to lowercase
// rune is for single charater
cleanWord := strings.TrimFunc(strings.ToLower(word), func(r rune) bool {
return !unicode.IsLetter(r)
})
if cleanWord != "" {
localFreq[cleanWord]++ // Add word in a local map and increment count
}
}
fmt.Printf("Goroutine %d: Finished counting local frequencies (map size: %d)\n", idx, len(localFreq))
// Merge into global map safely
fmt.Printf("Goroutine %d: Attempting to lock mutex for merging\n", idx)
mu.Lock() // aquire lock to modify global map
fmt.Printf("Goroutine %d: Acquired mutex lock\n", idx)
for word, count := range localFreq {
freqMap[word] += count
}
mu.Unlock()
fmt.Printf("Goroutine %d: Released mutex lock after merging\n", idx)
// Log completion
fmt.Printf("Goroutine %d: Finished processing and calling wg.Done()\n", idx)
}(i, content) // Nameless GO function ends here & called with index and string item
}
// Wait for all goroutines to finish
fmt.Println("Main: All goroutines launched; now waiting on WaitGroup...")
wg.Wait() // Control stops here and wait for all Goroutines to finish
fmt.Println("Main: WaitGroup complete; all goroutines finished. Proceeding to sort and print.")
// If no words found, handle gracefully
if len(freqMap) == 0 {
fmt.Println("No words found in the provided contents.")
return
}
// Prepare a slice for sorting
type wordFreq struct {
Word string
Count int
}
var freqList []wordFreq
// Similar to Javascript declare variables any where in code :( Good or Bad ?
for word, count := range freqMap { // Pay attention count is not index!!!
freqList = append(freqList, wordFreq{word, count})
}
fmt.Printf("Main: Prepared frequency list for sorting (size: %d)\n", len(freqList))
// Sort by count descending
sort.Slice(freqList, func(i, j int) bool {
return freqList[i].Count > freqList[j].Count
})
fmt.Println("Main: Sorted the frequency list.")
// Print top 5 (or fewer if less than 5)
topN := 5
if len(freqList) < topN {
topN = len(freqList)
}
fmt.Println("Top 5 most frequent words:")
for i := 0; i < topN; i++ {
fmt.Printf("%s: %d\n", freqList[i].Word, freqList[i].Count)
}
fmt.Println("Main: Program complete.")
}Ключевое слово GO запускает функцию асинхронно. В фоне создается goroutine для выполнения этой функции.

Если нужно, чтобы основной поток дождался завершения всех goroutine, используется WaitGroup.
waitgroup wg.add(N) показывает, что запущено N goroutine. Каждый defer wg.done() уменьшает счетчик на 1. Когда счетчик достигает нуля, ожидание завершается.
Mutex.Lock и Mutex.Unlock работают очевидным образом - они предотвращают одновременное выполнение участка кода несколькими потоками.
Если вы хотите глубже разобраться в параллелизме и разработке на Go, можно изучить материал курса Go-разработчик с нуля, где эти концепции разбираются на практике.
Хотите запустить и протестировать код - откройте его в Go Playground. Перед этим не забудьте поставить пару лайков :)
