go语言 5 并发

并发基础

并行:时刻同时运行
并发:单位时间内同时运行

并行时操作系统解决,并发由app解决

goroutine

Go例程,类似于线程
通过go+匿名函数启动goroutine

go func() {
    sum:=0
    for i :=0; i<1000; i++{
        sum+=1
    }
    println(sum)S
    time.Sleep(1*time.Second)
}()

println("NumGoroutine=", runtime.NumGoroutine())

time.Sleep(5*time.Second)

通过go+有名函数启动goroutine

//定义sum同上
func main(){
    go sum()
    ...
}

gotoutine特点:

  1. go执行时非阻塞的,不会等待
  2. go后面的函数返回值被忽略
  3. 不能保证多个goroutine的执行次序
  4. 没有父子goroutine的概念,所有例程平等
  5. go执行时为main创建一个goroutine
  6. 不能在一个goroutine里显式调用操作另一个goroutine,但是可以查看信息

func GOMAXPROCS(n int)

用来查询可以并发执行的goroutine数目,n>1表示设置,否则表示查询

runtime.GOMAXPROCS(2)

func Goexit

结束当前goroutine的运行,结束前会调用defer,不会产生panic

func Gosched

放弃当前调度执行机会,等待下一次执行

只有goroutine不够,多个goroutine之间还要通信,协同,后面介绍

chan

关键字
通道时goroutine之间通信的重要组件
Go不需要共享内存来通信,而是通过通信来共享内存
通道可以理解为 有类型的管道

make(chan datatype)//无缓冲通道,len,cap都是0
make(chan datatype,10)//创建一个有10个缓存的通道

len:没有被读取的元素数
cap:通道容量

无缓存通道用于:通信,以及两个make(chan datatype,10)的同步
有缓存通道用于:通信

waitgroup

157
sync包提供了多个goroutine同步

wait := &sync.WaitGroup{}
//或者 var wait sync.WaitGroup 新建一个结构
wait.Add(1)
wait.Done()
wait.Wait()

举例:
主程序读取切片,goroutine对切片处理,则:

for _,url := rangeurls{
    wait.Add(1)
    go func(url string){
        defer wait.Done()
        //do sth
    }(url)
}
wait.Wait()

通知退出机制

是后面context的基础
select用法,done的用法:https://www.cnblogs.com/dqh123/p/9481836.html

下游的消费者不需要随机数时,显式地通知生产者停止生产。

func GenerateIntA(done chan struct{}) chan int{
	ch:= make(chan int)
	go func(){
		Label:
			for{
				select {
				case ch <- rand.Int():
				case <-done:
					break Label
				}
			}
			close(ch)

	}()
	return ch
}
func main(){


	done :=make(chan struct{})
	ch := GenerateIntA(done)
	fmt.Println(<-ch)
	fmt.Println(<-ch)

	close(done)
	fmt.Println(<-ch)
	fmt.Println(<-ch)

	println("NumGoroutine=",runtime.NumGoroutine())
}

并发范式

每个范式是一个例子,模板,可以修改后用起来

生成器

生成器可以生成 全局事务号,订单号,随机数,序列号

  1. 上面的代码就是例子
  2. 增强型生成器:
//编写两个生成器,然后用扇入技术
func GenerateInt() chan int{
    ch := make(chan int,20)
    go func{
        for{
            select{
            case ch<-<-GenerateIntA:
            case ch<-<-GenerateIntB:
            }
       }
   }()
   return ch
}
func main(){
    ch := GenerateInt()
    for i :=0;i<100;i++{
        fmt.Println(<-ch)
    }
}

  1. 可以加入并发,缓冲,退出通知等多重特性

管道

通道有两个方向:读,写。
假如一个函数的输入输出都是同类型通道,它可以自己调用自己,形成调用链;或者多个相同类型的函数互相调用,也是一个调用链。就是管道

//chain()是一个函数,死循环从输入通道里读取,读不到了就关闭out,并返回out
out :=chain(chain(chain(in)))

每个请求一个goroutine

来一个请求就启动一个goroutine处理,典型的就是go的http server服务
例子:

package main

import (
	"fmt"
	"sync"
)

type task struct{
	begin int
	end int
	result chan<- int
}
func (t *task) do(){
	sum :=0
	for i:=t.begin;i<=t.end;i++{
		sum+=i
	}
	t.result <-sum
}
func main(){
	taskchan:= make(chan task,10)
	resultchan:= make(chan int,10)
	wait :=&sync.WaitGroup{}
    go Inittask(taskchan, resultchan, 10)
	go Distribute(taskchan, wait, resultchan)
	sum:= ProcessResult(resultchan)
	fmt.Println("sum=",sum)
}

//创建task并发送到task通道中,第一个task:{11,20,r},第二个:{21,30,r}
func Inittask(taskchan chan task, r chan int, p int) {
	qu:=p/10
	mod :=p%10
	high := qu*10
	for j :=0; j<qu; j++{
		b := 10*j+1
		e :=10*(j+1)
		tsk := task{
			begin:  b,
			end:    e,
			result: r,
		}
		taskchan<- tsk
	}
	if mod!=0{
		tsk:= task{
			begin:high+1,
			end:p,
			result:r,
		}
		taskchan<- tsk

	}
	close(taskchan)
}

//为每个task启动一个goroutine,等处理完成后关闭结果通道
func Distribute(taskchan chan task, wait *sync.WaitGroup, result chan int) {
	for v := range taskchan{
		wait.Add(1)
		go ProcessTask(v,wait)
	}
	wait.Wait()
	close(result)
}


func ProcessTask(t task, wait *sync.WaitGroup) {
	t.do()
	wait.Done()
}

//读取统计所有结果
func ProcessResult(resultchan chan int) int {
	sum:=0
	for r := range resultchan{
		sum+=r
	}
	return sum
}

固定worker工作池

服务器编程中,用线程池提升服务的并发处理能力。
go也可以构建固定数目的goroutine工作线程池。
例子:计算整数和
涉及到的goroutine:

  1. main的goroutine
  2. 初始化任务的
  3. 分发任务的
  4. 等待所有worker结束,关闭通道的

涉及到的通道:

  1. 传递task任务的通道
  2. 传递task结果的通道
  3. 接收worker处理完任务后所发送通知的通道done

例子略过,和上面的类似.

future模式

在一个流程中需要调用多个子调用的情况,子调用没有依赖,一个个调用费时。
方法:

  1. 使用chan作为函数参数
  2. 启动goroutine调用函数
  3. 通过chan传入参数
  4. 做其他可以并行处理的事情
  5. 通过chan异步获取结果

好处是把同步调用转换为异步调用。

type query struct{
	sql chan string
	result chan string
}
func execquery(q query){
	go func(){
		sql:= <-q.sql
		q.result<-"result from "+sql
	}()
}
func main(){
	q := query{make(chan string, 1), make(chan string,1)}
	go execquery(q)
	q.sql <-"select * from table"
	time.Sleep(1*time.Second)
	fmt.Println(<-q.result)
}

context标准库

多个goroutie如何协同工作?
通信:chan
同步:不带缓冲的chan,sync.WaitGrooup
通知:管理控制流数据,用两个chan,一个用于业务流数据,一个用于异常,然后用select收敛,这种方法不是通用的方法
退出:增加一个单独的通道,用select的广播机制退出

goroutine都是平级的,没有父子关系,但是一个go调用另一个go,再调用另一个go,其实是树状结构,怎样把树上的所有goroutine退出呢?用context
context提供两个功能:退出通知,元数据传递
congtext再内部维护一个调用树。

  1. 退出通知机制:通知可以传递给整个goroutine调用树上的每一个goroutine
  2. 传递数据:数据可以传递给整个goroutine调用树上的每一个goroutine

基本数据结构

root节点负责创建一个实现context接口的对象,并将对象作为参数传递到拉起的goroutine,下游的goroutine可以封装该对象,再传递到更下游
最后可以通过root节点遍历整个context树,消息和通知可以从root节点传递出去。

context接口:

//方法
Deadline()(deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}

canceler接口:
扩展接口,规定了取消通知的context类型需要实现的接口

cancel(removeFromParent bool, err error)
Done() <-chan struct{}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值