This page looks best with JavaScript enabled

Concurrency and parallelism in Go

 ·  ☕ 3 min read  ·  ✍️ t1

Concurrency and parallelism are important concepts in programming, and they are especially important in Go, a language designed for building highly concurrent systems. Concurrency refers to the ability of a program to handle multiple tasks concurrently, while parallelism refers to the ability of a program to execute multiple tasks simultaneously.

One reason why concurrency may not always be faster is because it can introduce overhead due to the need to coordinate and synchronize the concurrent tasks. For example, if the tasks being executed concurrently are not CPU-bound and instead are waiting on external resources such as I/O, the overhead of managing the concurrent tasks may outweigh the benefits of executing them concurrently.

CPU-bound workloads are tasks that require a lot of CPU processing power, while I/O-bound workloads are tasks that require a lot of input/output operations, such as reading and writing to a file or network. CPU-bound tasks can benefit from concurrency and parallelism because multiple tasks can be executed simultaneously on separate CPU cores, while I/O-bound tasks may not see as much benefit from concurrency and parallelism because they are often waiting on external resources.

In Go, channels and mutexes are two tools that can be used for synchronizing concurrent tasks. Channels are a way for goroutines (lightweight concurrent threads) to communicate with each other and synchronize their execution. Mutexes are used to protect shared resources from being accessed by multiple goroutines simultaneously, which can cause race conditions.

A data race occurs when two or more goroutines access the same memory location concurrently, and at least one of those accesses is a write. Data races can lead to race conditions, which are situations where the behavior of a program depends on the relative timing of concurrent tasks. Race conditions can be difficult to reproduce and debug, and they can lead to unexpected behavior in a program.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
	"fmt"
	"time"
)

func printNumbers(c chan int) {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		c <- i
	}
	close(c)
}

func printLetters(c chan string) {
	for i := 'A'; i < 'A'+10; i++ {
		time.Sleep(time.Second)
		c <- string(i)
	}
	close(c)
}

func main() {
	numbersChan := make(chan int)
	lettersChan := make(chan string)

	go printNumbers(numbersChan)
	go printLetters(lettersChan)

	for {
		select {
		case num, ok := <-numbersChan:
			if !ok {
				numbersChan = nil
			} else {
				fmt.Println(num)
			}
		case letter, ok := <-lettersChan:
			if !ok {
				lettersChan = nil
			} else {
				fmt.Println(letter)
			}
		}
		if numbersChan == nil && lettersChan == nil {
			break
		}
	}
}

This code example demonstrates concurrency by using two goroutines to print numbers and letters concurrently. The main function creates two channels, numbersChan and lettersChan, and starts the printNumbers and printLetters goroutines using these channels. The main function then uses a select statement to receive values from both channels concurrently, and prints the received values to the console.

Share on

t1
WRITTEN BY
t1
Dev