Golang - 并发编程

1. 并行和并发

  • 并行:在同一时刻,有多条指令在多个CPU处理器上同时执行
  • 2个队伍,2个窗口,要求硬件支持
  • 并发:在同一时刻,只能有一条指令执行,但多个进程指令被快速地轮换执行
  • 2个队伍,1个窗口,要求提升软件能力

2. go语言并发优势

  • go从语言层面就支持了并发
  • 简化了并发程序的编写

3. goroutine是什么

  • 它是go并发设计的核心
  • goroutine就是协程,它比线程更小,十几个goroutine在底层可能就是五六个线程
  • go语言内部实现了goroutine的内存共享,执行goroutine只需极少的栈内存(大概是4~5KB)

4. 创建goroutine

  • 只需要在语句前添加go关键字,就可以创建并发执行单元

    package main

    import (

    "fmt"

    "time"

    )

    //测试协程

    //循环打印内容

    func newTask() {

    i := 0

    for {

    i++

    fmt.Printf("new goroutine:i=%d\n", i)

    time.Sleep(1 * time.Second)

    }

    }

    //main()相当于是主协程

    func main() {

    //启动子协程

    go newTask()

    i := 0

    for {

    i++

    fmt.Printf("main goroutine:i=%d\n", i)

    time.Sleep(1 * time.Second)

    }

    }

  • 开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行

  • 如果主协程退出了,其他任务还执行吗?不执行

      package main
    
      import (
    "fmt"
    "time"
    ) //main()相当于是主协程
    func main() {
    //匿名子协程
    go func() {
    i := 0
    for {
    i++
    fmt.Println("子协程 i=", i)
    time.Sleep(1 * time.Second)
    }
    }()
    i := 0
    for {
    i++
    fmt.Println("主协程 i=", i)
    time.Sleep(1 * time.Second)
    //主协程第二次后退出
    if i == 2 {
    break
    }
    }
    }
  • 程序没任何输出,也不报错

      package main
    
      import (
    "fmt"
    "time"
    ) //main()相当于是主协程
    func main() {
    //匿名子协程
    go func() {
    i := 0
    for {
    i++
    fmt.Println("子协程 i=", i)
    time.Sleep(1 * time.Second)
    }
    }()
    }

5. runtime包

  • runtime.Gosched():用于让出CPU时间片,调度器重新安排任务调度,还是有几率分配到它的

      package main
    
      import (
    "fmt"
    "runtime"
    ) func main() {
    //匿名子协程
    go func(s string) {
    for i := 0; i < 2; i++ {
    fmt.Println(s)
    }
    }("world")
    //主协程
    for i := 0; i < 2; i++ {
    runtime.Gosched()
    fmt.Println("hello")
    }
    }
  • runtime.Goexit():立即终止当前协程

      package main
    
      import (
    "fmt"
    "time"
    "runtime"
    ) func main() {
    //匿名子协程
    go func() {
    defer fmt.Println("A.defer")
    //匿名函数
    func() {
    defer fmt.Println("B.defer")
    //此时只有defer执行
    runtime.Goexit()
    fmt.Println("B")
    }()
    fmt.Println("A")
    }()
    for {
    time.Sleep(time.Second)
    }
    }
  • runtime.GOMAXPROCS():设置并行计算的CPU核数,返回之前的值

      package main
    
      import (
    "runtime"
    "fmt"
    ) func main() {
    n := runtime.GOMAXPROCS(3)
    fmt.Println("n=%d\n",n)
    //循环执行2个
    for{
    go fmt.Print(0)
    fmt.Print(1)
    }
    }

6. channel是什么

  • goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,处理好线程安全问题
  • goroutine奉行通过通信来共享内存,而不是共享内存来通信
  • channel是一个引用类型,用于多个goroutine通讯,其内部实现了同步,确保并发安全

7. channel的基本使用

  • channel可以用内置make()函数创建

  • 定义一个channel时,也需要定义发送到channel的值的类型

      make(chan 类型)   //无缓冲的通道
    make(chan 类型, 容量) //有缓冲的通道
  • 当 capacity= 0 时,channel 是无缓冲阻塞读写的,当capacity> 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入

  • channel通过操作符<-来接收和发送数据,发送和接收数据语法:

      channel <- value   //发送value到channel
    <-channel //接收通道数据,并丢弃
    x := <-channel //通道取值并赋给x
    x, ok := <-channel //ok是检查通道是否关闭或者是否为空
  • channel基本使用

      package main
    
      import "fmt"
    
      func main() {
    //创建存放int类型的通道
    c := make(chan int)
    //子协程
    go func() {
    defer fmt.Println("子协程结束")
    fmt.Println("子协程正在运行...")
    //将666发送到通道c
    c <- 666
    }()
    //若已取出数据,下面再取会报错
    //<-c
    //主协程取数据
    //从c中取数据
    num := <-c
    fmt.Println("num = ", num)
    fmt.Println("主协程结束")
    }

8. 无缓冲的channel

  • 无缓冲的通道是指在接收前没有能力保存任何值的通道
  • 无缓冲通道,有可能阻塞

发送者 -> (通道(有可能有数据阻塞)) -> 接受者

package main

import (
"fmt"
"time"
) func main() {
//创建无缓冲通道
c := make(chan int, 0)
//长度和容量
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
//子协程存数据
go func() {
defer fmt.Println("子协程结束")
//向通道添加数据
for i := 0; i < 3; i++ {
c <- i
fmt.Printf("子协程正在运行[%d]:len(c)=%d,cap(c)=%d\n", i, len(c), cap(c))
}
}() time.Sleep(2 * time.Second)
//主协程取数据
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("num=", num)
}
fmt.Println("主协程结束")
}

9. 有缓冲的channel

  • 有缓冲的通道是一种在被接收前能存储一个或者多个值的通道

发送者 -> (通道(数据),(数据)(...)) -> 接受者

  • 上面代码创建时修改容量即可

      //创建有缓存的通道
    c :=make(chan int, 3)

10. close()

  • 可以通过内置的close()函数关闭channel

      package main
    
      import "fmt"
    
      func main() {
    //创建通道
    c := make(chan int)
    //子协程存数据
    go func() {
    for i := 0; i < 5; i++ {
    c <- i
    }
    //子协程close
    close(c)
    }()
    //主协程取数据
    for {
    if data, ok := <-c; ok {
    fmt.Println(data)
    } else {
    break
    }
    }
    fmt.Println("Finshed")
    }
  • 也可以如下遍历

      for data := range c{
    fnt.Println(data)
    }

11. 单方向的channel

  • 默认情况下,通道是双向的,也就是,既可以往里面发送数据也可以接收数据

  • go可以定义单方向的通道,也就是只发送数据或者只接收数据,声明如下

    var ch1 chan int //正常的

    var ch2 chan<- float64 //单向的,只用于写float64的数据

    var ch3 <-chan int //单向的,只用于读取int数据

  • 可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通channel

func main() {

//创建通道

c := make(chan int, 3)

//1. 将c准换为只写的通道
var send <- chan int =c //2. 将c转为只读的通道
var recv <- chan int =c //往send里面写数据
send < -1 //从recv读数据
<-recv

}

  • 单方向的channel有什么用?模拟生产者和消费者

      package main
    
      import "fmt"
    
      //生产者,只写
    func producter(out chan<- int) {
    //关闭资源
    defer close(out)
    for i := 0; i < 5; i++ {
    out <- i
    }
    } //消费者,只读
    func consumer(in <-chan int) {
    for num := range in {
    fmt.Println(num)
    }
    } func main() {
    //创建通道
    c := make(chan int)
    //生产者运行,向管道c存数据
    go producter(c)
    //消费者运行
    consumer(c)
    fmt.Println("done")
    }

12. 定时器

  • Timer:定时,时间到了响应一次

      package main
    
      import (
    "time"
    "fmt"
    ) func main() {
    //1.基本使用
    //创建定时器
    //2秒后,定时器会将一个时间类型值,保存向自己的c
    //timer1 := time.NewTimer(2 * time.Second)
    ////打印当前时间
    //t1 := time.Now()
    //fmt.Printf("t1:%v\n", t1)
    ////从管道中取出C打印
    //t2 := <-timer1.C
    //fmt.Printf("t2:%v\n", t2) //2.Timer只响应一次
    //timer2 := time.NewTimer(time.Second)
    //for {
    // <-timer2.C
    // fmt.Println("时间到")
    //} //3.通过Timer实现延时的功能
    ////(1)睡眠
    //time.Sleep(2*time.Second)
    //fmt.Println("2秒时间到")
    ////(2)通过定时器
    //timer3 := time.NewTimer(2 * time.Second)
    //<-timer3.C
    //fmt.Println("2秒时间到")
    ////(3)After()
    //<-time.After(2 * time.Second)
    //fmt.Println("2秒时间到") //4.停止定时器
    //timer4 := time.NewTimer(3 * time.Second)
    ////子协程
    //go func() {
    // <-timer4.C
    // fmt.Println("定时器器时间到,可以打印了")
    //}()
    //stop := timer4.Stop()
    //if stop {
    // fmt.Println("timer4已关闭")
    //} //5.重置定时器
    timer5 := time.NewTimer(3 * time.Second)
    //定时器改为1秒
    timer5.Reset(1 * time.Second)
    fmt.Println(time.Now())
    fmt.Println(<-timer5.C) for {
    }
    }
  • Ticker:响应多次

    package main

    import (

    "time"

    "fmt"

    )

    func main() {

    //创建定时器,间隔1秒

    ticker := time.NewTicker(time.Second)

     i := 0
    //子协程
    go func() {
    for {
    <-ticker.C
    fmt.Println(<-ticker.C)
    i++
    fmt.Println("i=", i)
    //停止定时器
    if i == 5 {
    ticker.Stop()
    }
    }
    }() //死循环
    for { }

    }

13. select

  • go语言提供了select关键字,可以监听channel上的数据流动

  • 语法与switch类似,区别是select要求每个case语句里必须是一个IO操作

      select {
    case <-chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
    default:
    // 如果上面都没有成功,则进入default处理流程
    } package main import (
    "fmt"
    ) func main() {
    //创建数据通道
    int_chan := make(chan int, 1)
    string_chan := make(chan string, 1)
    //创建2个子协程,写数据
    go func() {
    //time.Sleep(2 * time.Second)
    int_chan <- 1
    }()
    go func() {
    string_chan <- "hello"
    }()
    //如果都能匹配到,则随机选择一个去跑
    select {
    case value := <-int_chan:
    fmt.Println("intValue:", value)
    case value := <-string_chan:
    fmt.Println("strValue:", value)
    }
    fmt.Println("finish")
    }

14. 携程同步锁

  • go中channel实现了同步,确保并发安全,同时也提供了锁的操作方式

  • go中sync包提供了锁相关的支持

  • Mutex:以加锁方式解决并发安全问题

      package main
    
      import (
    "time"
    "fmt"
    "sync"
    ) //账户
    type Account struct {
    money int
    flag sync.Mutex
    } //模拟银行检测
    func (a *Account)Check() {
    time.Sleep(time.Second)
    } //设置账户余额
    func (a *Account)SetAccount(n int) {
    a.money +=n
    } //查询账户余额
    func (a *Account)GetAccount() int{
    return a.money
    } //买东西1
    func (a *Account)Buy1(n int){
    a.flag.Lock()
    if a.money>n{
    //银行检测
    a.Check()
    a.money -=n
    }
    a.flag.Unlock()
    } //买东西2
    func (a *Account)Buy2(n int){
    a.flag.Lock()
    if a.money>n{
    //银行检测
    a.Check()
    a.money -=n
    }
    a.flag.Unlock()
    } func main() {
    var account Account
    //设置账户余额
    account.SetAccount(10)
    //2个子协程买东西
    go account.Buy1(6)
    go account.Buy2(5)
    time.Sleep(2 * time.Second)
    fmt.Println(account.GetAccount())
    }
  • sync.WaitGroup:用来等待一组子协程的结束,需要设置等待的个数,每个子协程结束后要调用Done(),最后在主协程中Wait()即可

  • 引入

      package main
    
      import (
    "fmt"
    ) func main() {
    //创建通道
    ch := make(chan int)
    //count表示活动的协程个数
    count := 2
    go func() {
    fmt.Println("子协程1")
    //子协程1执行完成,给通道发送信号
    ch <-1
    }()
    go func() {
    fmt.Println("子协程2")
    ch <-1
    }()
    //time.Sleep(time.Second)
    //从ch中不断读数据
    for range ch{
    count --
    if count == 0{
    close(ch)
    }
    }
    }
  • go提供了这种解决方案sync.WaitGroup

  • Add():添加计数

  • Done():操作结束时调用,计数减去1

  • Wait():主函数调用,等待所有操作结束


未完待续...

Golang - 并发编程的更多相关文章

  1. golang并发编程

    golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...

  2. golang并发编程goroutine+channel(一)

    go语言的设计初衷除了在不影响程序性能的情况下减少复杂度,另一个目的是在当今互联网大量运算下,如何让程序的并发性能和代码可读性达到极致.go语言的并发关键词 "go" go dos ...

  3. golang并发编程的两种限速方法

    引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗 ...

  4. Golang并发编程基础

    硬件 内存 作为并发编程一个基础硬件知识储备,首先要说的就是内存了,总的来说在绝大多数情况下把内存的并发增删改查模型搞清楚了其他的基本上也是异曲同工之妙. 内存芯片--即我们所知道的内存颗粒,是一堆M ...

  5. Golang并发编程优势与核心goroutine及注意细节

    Go语言为并发编程而内置的上层API基于CSP(communication sequential processes,顺序通信进程)模型.这就意味着显式锁都是可以避免的,比如资源竞争,比如多个进程同时 ...

  6. Golang并发编程进程通信channel了解及简单使用

    概念及作用 channel是一个数据类型,用于实现同步,用于两个协程之间交换数据.goroutine奉行通过通信来共享内存,而不是共享内存来通信.引用类型channel是CSP模式的具体实现,用于多个 ...

  7. Golang并发编程——goroutine、channel、sync

    并发与并行 并发和并行是有区别的,并发不等于并行. 并发 两个或多个事件在同一时间不同时间间隔发生.对应在Go中,就是指多个 goroutine 在单个CPU上的交替运行. 并行 两个或者多个事件在同 ...

  8. Golang并发编程中select简单了解

    select可以监听channel的数据流动select的用法与switch语法非常类似,由select开始的一个新的选择块,每个选择条件由case语句来描述 与switch语句可以选择任何使用相等比 ...

  9. Golang并发编程有缓冲通道和无缓冲通道(channel)

    无缓冲通道 是指在接收前没有能力保存任何值得通道.这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作.如果两个goroutine没有同时准备好,通道会导 ...

随机推荐

  1. Android---keycode值以及相应名称

    KEYCODE列表 电话键 键名 描写叙述 键值   KEYCODE_CALL 拨号键 5 KEYCODE_ENDCALL 挂机键 6 KEYCODE_HOME 按键Home 3 KEYCODE_ME ...

  2. Codeforces Round #332 (Div. 2) B. Spongebob and Joke 模拟

    B. Spongebob and Joke     While Patrick was gone shopping, Spongebob decided to play a little trick ...

  3. luogu2770 航空路线问题 网络流

    题目大意: 给定一张航空图,图中顶点代表城市,边代表 2 城市间的直通航线.现要求找出一条满足下述限制条件的且途经城市最多的旅行路线.(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然 ...

  4. ES transport client批量导入

    从bulk.txt文件中按行读取,然后bulk导入.首先通过调用client.prepareBulk()实例化一个BulkRequestBuilder对象,调用BulkRequestBuilder对象 ...

  5. Codeforces--630C--Lucky Numbers(快速幂)

     C - Lucky Numbers Crawling in process... Crawling failed Time Limit:500MS     Memory Limit:65536K ...

  6. 【POJ 3630】 Phone List

    [题目链接] http://poj.org/problem?id=3630 [算法] 字典树 [代码] #include <algorithm> #include <bitset&g ...

  7. 97. ExtJS之EditorGridPanel afteredit属性

    转自:https://zccst.iteye.com/blog/1328869 1. 之前大多用Ext.grid.GridPanel,现在需要可编辑功能,发现比以前稍复杂一些. 就是需要对指定列进行可 ...

  8. Drainage Ditches(网络流(EK算法))

    计算最大流,EK算法模板题. #include <stdio.h> #include <string.h> #include <queue> using names ...

  9. Js:弹窗剧中

    js变量设置 var iWidth = $(window).width() * 0.9; var iHeight = $(window).height() * 0.9; - iHeight) / ; ...

  10. ubuntu 软件桌面图标创建

    sublime text 的安装目录是:/usr/local/sublimetext $cd 桌面 $vim Sublime\ Text.desktop 添加如下内容: [Desktop Entry] ...