【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

  待续

  

 

  

 

猜你喜欢

转载自www.cnblogs.com/GoodRnne/p/10846699.html