zoukankan      html  css  js  c++  java
  • 【Go】并发

    并发 


      在Go语言中较为出名的就是他本身支持高并发机制,通过在程序中使用关键字go  函数名() 来创建一个并发任务单元,然后系统将任务单元放置在系统队列中,等待调度器安排合适系统线程去获取执行权并执行任务单元(就是函数)。在这其中每个任务单元保存了该函数的指针、传入的参数、执行所需的栈内空间大小(2KB, 正是由于该原因所以才能创建成千上万的并发任务单元)。

      例子:

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func PrintTest(s string){
    	for i:= 0; i<5; i++ {
    		time.Sleep(100*time.Millisecond)     # 因为进程不会等待并发任务执行结束,所以设置一个睡眠时间来确保并发任务的执行。
    		fmt.Println(s)
    	}
    }
    
    func main(){
    	go PrintTest("hello")
    	PrintTest("word")
    } 
    word       // 可以看到word输出了5次,hello只输出了4次。
    hello
    hello
    word
    word
    hello
    hello
    word
    word
    

      为了避免这个情况我们可以使用channel进行阻塞解决这个问题。

    func PrintTest(s string){
    
    	for i:= 0; i<5; i++ {
    		fmt.Println(s)
    	}
    }
    
    func main(){
    	c := make(chan int)
    	go func(s string, c chan int){
    		for i:= 0; i<5; i++ {
    			fmt.Println(s)
    		}
    		close(c)
    	}("hello", c)
    	PrintTest("word")
    	<-c                 // 等待结束
    }
    

      当然如果是多个任务执行,还是建议使用sysc.WaitGroup来设置定计数器,统计并发单元的数量。直到为0时才退出程序。(这里注意一下就是虽然WaitGroup.Add()实现了原子操作,但是还是建议在goroutine外部实现累加计数器,避免在执行Add()的时候,主程序已经执行到wait部分,并判断为空退出执行了。)

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func main(){
    	var wg sync.WaitGroup
    	for i:= 0; i < 5; i++ {
    		wg.Add(1)        
    		go func(s int) {
    
    			for i := 0; i < 5; i++ {
    
    				fmt.Println(s)
    			}
    			wg.Done()    #  计数减去1
    		}(i)
    	}
    
    	wg.Wait()           # 等待结束
    }
    

      当然我们也可以使用多个wait()来控制任务的执行流程。

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    
    func main(){
    	var wg sync.WaitGroup
    		wg.Add(1)
    
    		go func(){
    			wg.Wait()
    			fmt.Println("func a wait")
    		}()
    go func() { time.Sleep(time.Second) fmt.Println("func b wait") for i := 0; i < 5; i++ { fmt.Println(i) } wg.Done() }() wg.Wait() }
    func b run        // 运行结果
    0
    1
    2
    3
    4
    func a wait
    

      通道


      在底层实现来说,通道只是一个队列。同步模式下,发送方和接收方双方匹配,然后直接复制数据给对方。如果配对失败,则进行等待,直到配对成功后才被唤醒。而异步模式下,需要通道设置缓冲区大小,这样发送方将数据写入通道之后并不需要接受方马上进行接收,而是在通道写满之后,如果接受方还未读取数据就会导致发送方阻塞,直到接受方将缓冲数据进行读取。

      未设置缓冲区的通道

    package main
    
    import "fmt"
    
    func sum(s []int, c chan int){
    	sum_ := 0
    	for _, i :=range(s){
    		sum_ += i
    	}
    	c<-sum_
    }
    
    func main(){
    	s := []int{1,2,3,4,5,6}
    	c := make(chan int)
    	go sum(s[:len(s)/2], c)
    	go sum(s[len(s)/2:], c)
    	x, y := <-c, <-c
    	fmt.Println(x,y)        // 输出   15  6
    
    }
    

       设置带有缓冲区的通道,我们可以一直添加数组(直到小于数组)

    package main
    
    import (
    	"fmt"
    )
    
    func sum(s []int, c chan int){
    	sum_ := 0
    	for _, i :=range(s){
    		sum_ += i
    		c<-sum_
    	}
    	close(c)
    }
    
    func main(){
    	s := []int{1,2,3,4,5,6}
    	c := make(chan int, 6)
    	go sum(s, c)
    
    	for i := range c{
    		fmt.Println(i)
    	}
    
    }
    

      当然除了传递数据之外,通道还可以用来进行时间通知。

    package main
    
    import (
    	"fmt"
    )
    
    func main(){
    	s := make(chan struct{})    # 结束事件管道
    	c := make(chan string)       # 数据管道
    	go func() {
    		t:= <- c
    		fmt.Println(t)
    		close(s)
    	}()
    
    	c <-"hi"      # 发送消息
    	<-s            # 等待结束
    }
    

      例子2

    package main
    
    import "fmt"
    
    func main(){
    	s := make(chan struct{})
    	c := make(chan int)
    	go func() {
    		defer close(s)
    		for {
    			x, err := <-c        # 判断通道是否已经关闭
    			if !err{
    				return
    			}
    			fmt.Println(x)
    		}
    	}()
    	c <- 1
    	c <- 2
    	c <- 3
    	close(c)   
    	<- s    # 等待通道关闭
    }
    

      创建通道的时候,默认是双向的。但是有时候我们也可以使用单向通道来管理程序逻辑的执行顺序,(如果违背了单项通道的发送方向,则会无效操作, 另外还有就是在接收端不能对通道进行关闭,无效操作)

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func main(){
    	var wg sync.WaitGroup
    	wg.Add(2)
    
    	c := make(chan int)
    	var send chan<- int = c    // 定义单向发送通道,无法将其转换回双向通道
    	var recv <-chan int = c       // 定义单项接收通道, 无法将其转换双向通道
    
    	go func() {
    		defer wg.Done()
    		for x:= range recv{
    			fmt.Println(x)
    		}
    	}()
    	
    	go func() {
    		defer wg.Done()
    		defer close(c)
    		for i := 0; i<3; i++{
    			send <- i
    		}
    	}()
    	wg.Wait()
    }
    

      在处理多个通道的时候,我们可以使用select语句,她会随机选择一个可用通道做收发操作, 当然即使case中都是同一个通道,select在选择的时候也会随机选择。

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func main(){
    	var wg sync.WaitGroup
    	wg.Add(2)
    
    	c, b := make(chan int), make(chan int)          // 创建两个通道
    
    
    	go func() {                        // 接收函数
    		defer wg.Done()
    		var (
    			name string
    			x int
    			ok bool
    		)
    		for {
    			select {                 // 随机选择一个通道进行接收数据
    			case x, ok = <-c:
    				name = "a"
    			case x, ok = <-b:
    				name = "b"
    			}
    			if !ok {
    				return
    			}
    			fmt.Println(name, x)
    		}
    	}()
    
    	go func() {
    		defer wg.Done()              
    		defer close(c)           
    		defer close(b)
    
    		for i := 0; i<6; i++{        // 随机选择一个通道进行发送
    			select {
    			case c <- i:
    			case b <- i*10:
    			}
    		}
    	}()
    	wg.Wait()
    }
    
    a 0     // 输出结果
    b 10
    a 2
    b 30
    b 40
    a 5
    

      待续

      

     

      

     

  • 相关阅读:
    Oracle+PL+SQL从入门到精通.丁士锋.清华大学出版社.2012
    《 Oracle查询优化改写 技巧与案例 》电子工业出版社
    HTML5从入门到精通(明日科技) 中文pdf扫描版
    《JavaWeb从入门到精通》(明日科技,清华大学出版社)
    JavaScript从入门到精通(附光盘1张):作者:明日科技出版社:清华大学出版社出版时间:2012年09月
    ORACLE_修改实例的内存大小
    JAVA图书管理系统汇总共27个
    IntelliJ Idea 2017 免费激活方法
    spring springMvc spring-boot spring-cloud分别是什么
    nodejs环境 + 入门 + 博客搭建
  • 原文地址:https://www.cnblogs.com/GoodRnne/p/10846699.html
Copyright © 2011-2022 走看看