• Goroutine

并发x并行x串行

并发: 并发是一个核运行多线程程序,说人话就是一个程序分为好多个子程序,然后一个人干,这个人一会干这个一会干那个,类似于ABCBCABCA….这样子干,因为计算机运行速度很快,就给人一种“多线程”的感觉,一个人在同时做很多事情

并行: 并行就是多核运行多线程程序,说人话就是多个人一起干多个程序

串行: 多线程程序中,要先干完线程A,再干线程B……

并发就是一个人同时吃三个馒头,并行是三个人吃三个馒头

在Go实战中描述

  • 并行是让不同的代码片段在不同的物理处理器上执行,关键是同时做很多事情
  • 并发是指同时管理很多事情,这些事情可能做了一半就被暂停就去做别的事情了

一般来说,我们总觉得并行更好,因为他多步进行,但是事实上操作系统上的资源总是少得可怜,从”使用较少的资源做更多的事情”的哲学出发,并发只使用了很少的资源,却能够管理很多事情,注意这里是管理。

线程和协程

线程: 内核态,线程跑多个协程,栈MB级别

协程: 用户态,轻量级线程,栈KB级别

没啥好理解的,反正协程就是轻量

1
2
3
4
5
6
7
8
9
10
func hello(i int) {
println("hello goroutine :" + fmt.Sprint(i))
}

func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go hello(i) // 使用 go 关键字开启协程
}
time.Sleep(time.Second * 1) // 保证子协程执行完毕之前,主函数不退出,后面会有更好的方式
}
1
2
3
4
5
6
print:
hello goroutine: 4
hello goroutine: 3
hello goroutine: 0
hello goroutine: 2
hello goroutine: 1

再次理解一下

进程 = ==内存== + ==文件和设备的句柄== + ==CPU调度器==(线程)

每个进程至少包含一个线程,每个进程初始线程被称作主线程,因此我们常常会使用sync.wait来使主goroutine来等待其他协程的完成,如果主goroutine先关闭,其他协程也会关闭

进程是一个大的空间,包含程序执行的各种资源

线程是一个执行的空间

多线程——多CPU

而一个线程上有一个==逻辑处理器==(Go特有),而这个逻辑处理器处理的就是协程

我们可以把这个逻辑处理器看作一个队列,而开启一些goroutine就是让这些goroutine入队,逻辑的含义就是系统会通过一些算法使得多个goroutine按顺序运行,同时需要明白的是:运行顺序并不是goroutineA,goroutineB… 而是会在某个goroutine运行到某个阶段去运行其他goroutine,具体运行到什么阶段 就是算法的事情了


竞争状态

如果多个或者多个 goroutine 在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就会处于相互竞争的状态。

这个状态我们可以从Mysql的幻读等缺陷可以类比

因此我们需要锁住共享资源

  • sync.Mutex

    互斥锁,互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区的代码

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
var (
x int64
lock sync.Mutex // 创建一个锁
)

func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock() //加锁
x += 1
lock.Unlock() // 解锁
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1 //不加锁
}
}

func Add() {
x = 0
for i := 0; i < 5; i++ { // 连开 5 个协程
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ { // 连开 5 个协程
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
1
2
3
print:
goroutineWithLock 10000
goroutineWithoutLock 8156
  • atomic包

    这个包的名字就是原子函数,从原子性就可以知道他的作用是什么
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main(){
w.Add(2)

go incCounter(1)
go incCounter(2)

w.Wait()
fmt.Println("Final Counter:", counter)

}

func incCounter(id int){
defer w.Done()

for count := 0; count < 2; count++ {
// 安全地对counter加1
atomic.AddInt64(&counter, 1)

// 从当前goroutine退出,并放回到队列
runtime.Gosched()
}
}

==LoadInt64== 和 ==StoreInt64== 提供了读和写的方式,可以试试

time.S

leep()太呆板了,换个高级的sync.WaitGroup

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
var (
x int64
lock sync.Mutex
wg sync.WaitGroup
)

func addWithLock() {
defer wg.Done()
for i := 0; i < 2000; i++ {
lock.Lock()
x = x + 1
lock.Unlock()
}
}

func main() {
x = 0
wg.Add(5)
for i := 0; i < 5; i++ {
go addWithLock()
}
wg.Wait()
fmt.Println("addWithLock:", x)
}

1
2
print:
addWithLock: 10000

Channel

请跳转到Channel那篇笔记中