Go 学习之并发

 

1.并发与并行的区别

       并行:多核cpu在同一时间片内并行处理多个任务。
      并发:如单核cpu在多个任务间进行时间片切换,并非同一时间片执行多个任务,只是上下文切换时间很短,看似多个任务并行。

      多线程和多线程是并行的基本前提条件,单线程也可用协程做到并发。

      在golang中是通过goroutine来实现并发的,goroutine并不能简单的归纳为协程,其运行时会创建多个线程来实现并发任务,且任务单元可被调度到其他线程并行执行。所以goroutine更像是多线程和协程的综合体,能最大限度提升执行效率,发挥多核处理能力。

goroutine

     关键字go并非执行并发操作,而是创建一个并发任务单元。新建任务被放置在系统队列中,等待调度器安排合适的系统线程去获取执行权。
    当前流程不会阻塞,不会等待该任务启动,且运行时也不保证并发任务的执行顺序。

     每个任务单元除保存函数指针、调用参数外,还会分配执行所需的栈内存空间。相比系统默认MB级别的线程栈,goroutine自定义栈仅需2KB,所以才能创建成千上万的并发任务。自定义栈采取按需分配策略,在需要时仅需扩容,最大能到GB规模。

    与defer一样,goroutine也会因延迟执行而立即计算并复制执行参数。

var c int
func counter()int{
    c++
    return c
}
func main() {
    a:=100
    go func(x,y int) {
        time.Sleep(time.Second)
        fmt.Println("go:",x,y)
    }(a,counter())
    a+=100
    fmt.Println("main:",a,counter())
    time.Sleep(time.Second*3)
}

输出:

main: 200 2
go: 100 1

wait

     进程退出时不会等待并发任务结束,可用channel阻塞,然后发出退出信号。

func main() {
    exit:=make(chan interface{})  //创建通道。因为仅是通知,此处channel可为任何类型。
    go func() {
        time.Sleep(time.Second)
        fmt.Println("goroutine done")
        close(exit)                      //关闭通道,发出信号。
    }()

    fmt.Println("main...")
    <-exit                             //通道关闭则立即解除。
    fmt.Println("main exit")
}

输出:

main...
goroutine done
main exit

     除了关闭通道外,向通道内写入数据也可解除阻塞。channel的更多信息,后面再做详述。

    如要等待多个任务结束,推荐使用sync.WaitGruop。通过设定计数器,让每个goroutine在退出前递减,直至归零时解除阻塞。

func main() {
    var wg sync.WaitGroup

    for i:=0;i<10;i++{
        wg.Add(1)         //累加计数
        go func(id int) {
            defer wg.Done()        //递减计数
            time.Sleep(time.Second)
            fmt.Println("goroutine",id,"done")

        }(i)
    }

    fmt.Println("main...")
    wg.Wait()                   //阻塞,直到计数归零
    fmt.Println("main exit")
}

        尽管WaitGroup.Add实现了原子操作,但建议在goroutine外累加计数器,以免Add尚未执行,Wait以及推出。

func main() {
    var wg sync.WaitGroup
    go func() {
        wg.Add(1)           //可以运行试一下,不是每次都能设置上
        defer wg.Done() //递减计数
        fmt.Println("goroutine", "done")

    }()

    fmt.Println("main...")
    wg.Wait() //阻塞,直到计数归零
    fmt.Println("main exit")
}

       可在多处用Wait阻塞,他们都能接收到通知。上例就可在go func前加wg.Wait().

GOMAXPROCS

    运行时可能会创建很多线程,但任何时候仅有限的几个线程参与并发任务执行。该数量默认与CPU核数相等,可用runtime.GOMAXPROCS函数(或环境变量)修改。

    如参数小于1,GOMAXPROCS仅返回当前设置值,不做任何调整。

import (
    "math"
    "fmt"
    "sync"
    "runtime"
)

//测试目标函数
func count(){
    x:=0
    for i:=0;i<math.MaxUint32;i++{
        x+=i
    }
    fmt.Println(x)
}

//循环执行
func test(n int){
    for i:=0;i<n;i++{
        count()
    }
}

//并发执行
func test2(n int){
    var wg sync.WaitGroup
    wg.Add(n)
    for i:=0;i<n;i++{
        go func() {
            count()
            wg.Done()
        }()
    }
    wg.Wait()
}

func main() {
    n:=runtime.GOMAXPROCS(0)
    n1:=runtime.NumCPU()
    fmt.Println(n1)
    test(n)
}
 n:=runtime.GOMAXPROCS(0)
 n1:=runtime.NumCPU()

     上述两个都可用来获取当前系统的cpu核数。

 

Local Storage

     与线程不同,goroutine任务无法设置优先级,无法获取编号,没有局部存储(TLS),甚至连返回值都会被抛弃。但除优先级外,其他功能都很容易实现。

func main() {
    var wg sync.WaitGroup
    var gs [5]struct{             //用于实现类似TLS功能
        id  int  //编号
        result  int //返回值
    }
    for i:=0;i<len(gs);i++{
        wg.Add(1)
        go func(id int) {   //使用参数避免参数闭包延迟求值
            defer wg.Done()
            gs[id].id = id
            gs[id].result=(id + 1) * 100
        }(i)
    }
    wg.Wait()
    fmt.Printf("%+v\n",gs)
}

  输出:

[{id:0 result:1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值