zoukankan      html  css  js  c++  java
  • Golang 基础 11 -- 并发 concurrency

    注意事项

    • goroutine只是官方的超级线程池
    • 高并发性:占用内存小,创建销毁很快
    • goroutine的简单易用,也在语言层面上给予开发者巨大的便利
    • 并发不是并行,并行是直接利用多核实现多线程的运行,并发是由切换时间片来实现“同时”运行
    • goroutine奉行通过通信(channel)来共享内存,而不是共享内存来通信

    channel

    • channel是goroutine沟通的桥梁,大都是阻塞同步的
    • 通过make创建,close关闭
    • channel是引用类型,也就是说,传入的参数可以直接对他本身操作
    func main(){
    	c := make(chan bool)
    	go func(){
    		fmt.Println("GO GO GO")
    		c <- true
    	}()
    	<- c
    }
    /*
    > Output:
    command-line-arguments
    GO GO GO
    */
    
    • 可以使用for range来迭代不断操作channel
    • 迭代的时候记得一定要在某个地方调用close(c)关闭,以避免造成死锁
    func main(){
    	c := make(chan bool)
    	go func(){
    		fmt.Println("GO GO GO")
    		c <- true
    		close(c)
    	}()
    	for v := range c {
    	//程序运行到这,一直等待着c会有值
    		fmt.Println(v)
    	}
    }
    
    
    • 可以设置单向或者双向通道
    • 可以设置缓存大小,在未被填满前不会发生阻塞
      关于缓存,可参考:Go语言_并发篇
      这里写图片描述
      有缓存是异步的,无缓存是同步阻塞的
      有缓存要先放
      无缓存要先取

    乱序及缺失问题..whatever

    func main(){
    	runtime.GOMAXPROCS(runtime.NumCPU())
    	c := make(chan bool)
    	for i:=0; i<10 ; i++ {
    		go Go(c,i)
    	}
    	<-c
    }
    func Go(c chan bool, index int){
    	a := 1
    	for i:=0;i<1000000;i++{
    		a += i
    	}
    	fmt.Println(index,a)
    
    	if index==9 {
    		c <- true
    	}
    }
    /*
    > Output:
    command-line-arguments
    1 499999500001
    0 499999500001
    2 499999500001
    9 499999500001
    */
    

    以上输出十分不符合圣意,大臣们献出以下两个计策

    计策一:给channel加buffer

    func main(){
    	runtime.GOMAXPROCS(runtime.NumCPU())
    	c := make(chan bool,10)
    	for i:=0; i<10; i++ {
    		go Go(c,i)
    	}
    	for i:=0;i<10;i++{
    		<- c
    	}
    }
    func Go(c chan bool, index int){
    	a := 1
    	for i:=0;i<1000000;i++{
    		a += i
    	}
    	fmt.Println(index,a)
    
    	c <- true
    }
    /*
    > Output:
    command-line-arguments
    2 499999500001
    1 499999500001
    9 499999500001
    0 499999500001
    6 499999500001
    3 499999500001
    5 499999500001
    7 499999500001
    8 499999500001
    4 499999500001
    */
    

    计策二:引入sync包

    package main
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    func main(){
    	//(runtime包是goroutine的调度器),runtime.GOMAXPROCE设置允许同时最多使用多少个核
    	runtime.GOMAXPROCS(runtime.NumCPU())
    	wg := sync.WaitGroup{}
    	for i:=0; i<10; i++ {
    		//waitgroup作为结构,使用&进行地址传递
    		go Go(&wg,i)
    	}
    	wg.Wait()
    }
    func Go(wg *sync.WaitGroup, index int){
    	a := 1
    	for i:=0;i<1000000;i++{
    		a += i
    	}
    	fmt.Println(index,a)
    
    	wg.Done()
    }
    

    select

    • 专门为了channel设计的结构
    func main(){
    	c1,c2 := make(chan int),make(chan string)
    	o := make(chan bool)
    	go func(){
    		for{
    			select{
    			case v,ok := <-c1:
    				if !ok{
    					o <- true
    					break
    				}
    				fmt.Println("c1",v)
    			case v,ok := <-c2:
    				if !ok{
    					o <- true
    					break
    				}
    				fmt.Println("c2",v)
    			}
    		}
    	}()
    	c1 <- 1
    	c2 <- "hi"
    	c1 <- 3
    	c2 <- "lel"
    
    	close(c1)
    	//close(c2)
    	for i:=0;i<2;i++{
    		<- o
    	}
    }
    //没办法同时记录两个select的执行次数
    
    • 可以处理一个或者多个channel的发送与接收
    func main(){
    	c := make(chan int)
    	go func(){
    		i := 0
    		for v:= range c {
    			i ++
    			fmt.Println(v)
    			if i>10{
    				break
    			}
    		}
    	}()
    
    	for {
    		select {
    		case c <- 0:
    		case c <- 1:
    		}
    	}
    }
    //运行结果很鬼畜,,
    
    • 同时有多个可用的channel时按随机顺序处理
    • 可用空的select来阻塞main函数
    • 可设置超时
    package main
    import (
    	"fmt"
    	"time"
    )
    func main(){
    	c := make(chan bool)
    	select {
    	case v := <- c:
    		fmt.Println(v)
    	case <-time.After(3*time.Second):
    		fmt.Println("timeout")
    	}
    }
    //time.After()返回一个time型的chan
    

    例子:用goroutine实现发送接收数条消息

    package main
    
    import (
    	"fmt"
    )
    
    var cc chan string
    
    func main() {
    	cc = make(chan string)
    
    	go Go()
    	for i := 0; i < 5; i++ {
    		cc <- fmt.Sprintf("From main:hello, #%d", i)
    		fmt.Println(<-cc)
    	}
    
    }
    func Go() {
    	for i := 0; ; i++ {
    		fmt.Println("djd", <-cc)
    		cc <- fmt.Sprintf("From Go:hi,#%d", i)
    	}
    }
    /*
    	djd From main:hello, #0
    	From Go:hi,#0
    	djd From main:hello, #1
    	From Go:hi,#1
    	djd From main:hello, #2
    	From Go:hi,#2
    	djd From main:hello, #3
    	From Go:hi,#3
    	djd From main:hello, #4
    	From Go:hi,#4
    */
    
  • 相关阅读:
    SpringCloud-Bus总线的其他应用场景(发布-订阅)
    Java的静态代理和动态代理
    为什么重写equals还要重写hashcode?(转载)
    js通过replace()方法配合正则去除空格
    2016-08-15T16:00:00.000Z 格式转换成yyyy-MM-dd HH:mm:ss格式
    js任意数组按下标相加
    学习笔记《Java多线程编程实战指南》四
    学习笔记《Java多线程编程实战指南》三
    学习笔记《Java多线程编程实战指南》二
    学习笔记《Java多线程编程实战指南》一
  • 原文地址:https://www.cnblogs.com/leafs99/p/golang_basic_11.html
Copyright © 2011-2022 走看看