Post

Day11(并发编程)

概念

  • 并发:把任务在不同的时间点交给处理器进行处理同一时间点,任务并不会同时进行
  • 并行:把每一个任务分配给每一个处理器独立完成,在同一时间点任务一定是同时运行的

简介使用

  • 创建:使用 go 关键字创建一个 goroutine go functionName()
  • 调整并发运行性能

    • 例子:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      runtime.GOMAXPROCS(number)
      number -> 逻辑CPU数量
      number < 1 不修改任务参数
             = 1 单核心执行
             > 1 多核并发执行
      
      cpuNUmber := runtime.NumCPU() // 查询CPU数量
      fmt.Println(cpuNUmber)
      runtime.GOMAXPROCS(cpuNUmber)
      
    • 批注

      • Go1.5 之前 默认使用单核执行,之后默认执行上面语句,以便让代码并发执行,最大效率的利用 CPU
      • GOMAXPROCS 也是环境变量,在应用启动前设置环境变量也可以起到相同的作用

通道(channel)

在多个 goroutine 间通信的管道

并发编程

goroutine

  • 时间片轮转以及调度 goroutine 协程 使用 go 关键字创建,并发执行
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
package main

import (
	"fmt"
	"time"
)

func task() {
	var i int
	for {
		i++
		fmt.Println("new Task")
		if i == 3 {
			break
		}
		time.Sleep(time.Second)
	}
}

func main() {
	go task()
	var i int
	for {
		i++
		fmt.Println("main func")
		if i == 3 {
			break
		}
		time.Sleep(time.Second)
	}
}
1
2
3
4
5
new Task
main func
main func
new Task
main func
  • 主协程退出其他子协程也会退出

    下面程序只会打印 main

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main(){
	go func ()  {
		for i := 0; i < 5; i++ {
			fmt.Println("子协程")
		}
	}()
	fmt.Println("main")
}
  • runtime工具包

    GoschedGoexitGOMAXPROCS

    • Gosched 让出时间片 先让别的协程,执行再执行此协程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func main() {
    	go func() {
    		for i := 0; i < 3; i++ {
    			fmt.Println("Cc")
    		}
    	}()
    	for i := 0; i < 2; i++ {
    		runtime.Gosched()
    		fmt.Println("Lcc")
    	}
    }
    
    1
    2
    3
    4
    5
    
    Cc
    Cc
    Cc
    Lcc
    Lcc
    
    • Goexit 终止此协程与 return 不同后者终止次函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func exit() {
    	fmt.Println("exit a")
    	// return
    	runtime.Goexit()
    	fmt.Println("exit b")
    }
    
    func main() {
    	go func() {
    		fmt.Println("go func 1")
    		exit()
    		fmt.Println("go fun 2")
    	}()
    	// 等待
    	time.Sleep(time.Second * 5)
    }
    
    1
    2
    
    go func 1
    exit a
    
    • GOMAXPROCS指定核数运行 1~4 时打印交叉
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func main() {
    	runtime.GOMAXPROCS(4)
    	for i := 0; i < 100; i++ {
    		go fmt.Print(1)
    		fmt.Print(0)
    	}
    	time.Sleep(time.Second * 2)
    }
    

channel

资源竞争共享内存

  • 每使用 channel 造成资源竞争和共享内存问题,两个协程同时使用 printer打印值会造成不一,不会同步,把相关的 ch 注释且可以解决
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
package main

import (
	"fmt"
	"time"
)
// var ch = make(chan int)
func printer(word string) {
	for _, v := range word {
		fmt.Printf("%c", v)
		time.Sleep(time.Second)
	}
}

// lcc 打印 title
func lcc() {
   //  ch <- 1
	printer("title")
}

// Cc 打印 word
func Cc() {
  //  <- ch
	printer("word")
}

func main() {
	go lcc()
	go Cc()
	time.Sleep(time.Second * 10)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"fmt"
)

func main() {
	//创建channel
	ch := make(chan string)
	defer fmt.Println("main  end")
	go func() {
		defer fmt.Println("func end")
		for i := 0; i < 2; i++ {
			fmt.Println("Func for")
		}

		ch <- "ch wr"

	}()

	str := <-ch //没有数据前,阻塞
	fmt.Println("rd  = ", str)
}
  • 无缓冲与有缓冲

    • 无缓冲接收前,不会保存任何通道值,需要发送端和接收端同时准备好才能完成操纵,则会造成堵塞等待
    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
    
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	//创建一个无缓存的channel
    	ch := make(chan int, 0)
    
    	//len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小
    	fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
    
    	//新建协程
    	go func() {
    		for i := 0; i < 3; i++ {
    			fmt.Printf("子协程:i = %d\n", i)
    			ch <- i //往chan写内容
    		}
    	}()
    
    	//延时
    	time.Sleep(2 * time.Second)
    
    	for i := 0; i < 3; i++ {
    		num := <-ch //读管道中内容,没有内容前,阻塞
    		fmt.Println("num = ", num)
    	}
    
    }
    
    • 有缓存接收前,会保存一个或多个通道值,只有通道没有接收值才会堵塞
    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
    
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	//创建一个有缓存的channel
    	ch := make(chan int, 3)
    
    	//len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小
    	fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
    
    	//新建协程
    	go func() {
    		for i := 0; i < 10; i++ {
    			ch <- i //往chan写内容
    			fmt.Printf("子协程[%d]: len(ch) = %d, cap(ch)= %d\n", i, len(ch), cap(ch))
    		}
    	}()
    
    	//延时
    	time.Sleep(2 * time.Second)
    
    	for i := 0; i < 10; i++ {
    		num := <-ch //读管道中内容,没有内容前,阻塞
    		fmt.Println("num = ", num)
    	}
    
    }
    
  • 关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

var ch = make(chan int)

func main() {
	go func() {
		for i := 0; i < 5; i++ {
			ch <- i
		}
		close(ch)
	}()

	for {
		if i, ok := <-ch; ok == true {
			fmt.Println(i)
		} else {
			break
		}
	}
}
1
2
3
4
5
0
1
2
3
4
  • 单方向 channel(生产者、消费者)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

// write 写
func write(write chan<- string) {
	for i := 0; i < 3; i++ {
		write <- fmt.Sprint("Cc", i)
	}
	defer close(write)
}

// read 读
func read(read <-chan string) {
	for v := range read {
		fmt.Println(v)
	}
}

func main() {
	ch := make(chan string)
	go write(ch)
	read(ch)
}
1
2
3
Cc 写0
Cc 写1
Cc 写2

sync

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
26
27
28
package main

import (
	"fmt"
	"sync"
)

func main() {
	var group sync.WaitGroup
	// 添加两个
	group.Add(2)
	go func() {
		for i := 0; i < 3; i++ {
			fmt.Println("func1 --->", i)
		}
		// 执行完之后去除一个执行完毕
		group.Done()
	}()
	go func() {
		for i := 0; i < 4; i++ {
			fmt.Println("func2 --->", i)
		}
		// 去除一个执行完毕
		group.Done()
	}()
	// 等待group 数量为0 ,如果Done()方法数量和添加数量不一至,会造成deadlock! 死锁!
	group.Wait()
}
1
2
3
4
5
6
7
func2 ---> 0
func2 ---> 1
func2 ---> 2
func2 ---> 3
func1 ---> 0
func1 ---> 1
func1 ---> 2
This post is licensed under CC BY 4.0 by the author.