Go语言实现了CSP(通信顺序进程)模型来作为goroutine间的推荐通信方式

# 并发和并行

  • 并发:同一时间内执行多个任务
  • 并行:同一时刻执行多个任务

Go语言的并发通过goroutine实现。goroutine类似于线程,属于用户态的线程。操作系统的线程属于内核级的线程。

我们可以根据需要创建成千上万个goroutine并发工作。

# golang的CSP并发模型

CSP(Communicating Sequential Process)并发模型,中文可以叫做通信顺序进程

CSP 模型由并发执行的实体(线程或者进程)所组成,实体之间通过发送消息进行通信, 这里发送消息时使用的就是通道,或者叫 channel

请记住下面这句话: Do not communicate by sharing memory; instead, share memory by communicating. 不要以共享内存的方式来通信,相反,要通过通信来共享内存。

普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。

非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问, 因此,在很多时候,衍生出一种方便操作的数据结构,叫做线程安全的数据结构 例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。

Go的CSP并发模型,是通过goroutine和channel来实现的。

goroutine 是Go语言中并发的执行单位。 channel 是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

# goroutine

go语言中使用goroutine非常简单,只需要在函数前面加上go关键字

var wg sync.WaitGroup

func say(s string, i int) {
	fmt.Println(s, i)
	wg.Done()
}

func main() {
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		//go say("hello", i)
		go func(i int) {
			fmt.Println(i)
			wg.Done()
		}(i)
	}

	wg.Wait()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

可增长的栈

OS线程(操作系统线程)一般都有固定的栈空间(通常为2MB)

goroutine初始状态为2kb, 可以增长到1G

runtime.GOMAXPROCS(4) // 设置用几个cpu运行代码

# channel

go的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存,而不是通过共享内存而实现通信

goroutine是go程序并发的执行体,channel是他们之间的连接

channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制

var ch1 chan int // 声明一个传递整型的通道
ch1 = make(chan int, 1)
ch1 <- 10 // 发送
x := <-ch1 // 接收
close(ch1) // 关闭通道
1
2
3
4
5
ch1 = make(chan int, 1) // 带缓冲区通道
ch2 = make(chan int) // 无缓冲区通道,又称为同步通道
1
2

# goroutine + channel 协同工作

func producer(ch chan int) {
	for i := 0; i < 100; i++ {
		ch <- i
	}
	close(ch)
}

func consumer(ch1 chan int, ch2 chan int) {
	// 从通道中取值的方式1
	for {
		value, ok := <-ch1
		if !ok {
			break
		}
		ch2 <- value * value
	}
	close(ch2)
}

func main() {
	ch1 := make(chan int, 100)
	ch2 := make(chan int, 100)
	go producer(ch1)
	go consumer(ch1, ch2)

	// 从通道中取值的方式2
	for ret := range ch2 {
		fmt.Println(ret)
	}
}
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

select多路复用

select {
	case <-ch1:
		break
	case <-ch2:
		break
	default:
        // 默认操作
}
1
2
3
4
5
6
7
8