Go基础编程---并发

并行和并发

并行: 指在同一时刻,有多条指令在多个处理器上同时进行
并发:同一时刻只能有一条指令执行,但是多个指令被快速的轮换执行,在宏观上体现出过个进程同时执行的效果,微观上是把时间分成若干段,使得多个进程快速交替执行

goroutine(协程)--- 并发核心

执行goroutine只要极少的栈内存(4-5kb),自动伸缩,goroutine比thread更易用、高效、轻便

// 创建一个goroutine : 只要在函数调用语句前添加go 关键字,就可以创建并发执行单元--协程
func add(){
  for{
    fmt.Println("bbbb")
  }
}
func main(){
  go add()
  for{
    fmt.Println("xxxx")
  }
  // 打一部分 xxx 打一部分 bbb
}

主协程退出,其余子协程都将被关闭--有可能主协程退出太快,导致其他子协程没调用就退出

runtime包

// runtime.Gosched 短暂让出cpu的时间片,让其他协程执行,不定时后再回到此协程
func main() {

    for i := 0; i < 10; i++ {
        go showNumber(i)
    }
    runtime.Gosched()
    fmt.Println("Haha")
    // 1,0,9, haha
    // 1,2,3,6,haha  每次运行有不同结果
    // runtime.Gosched()是短暂让出cpu的时间片,让其他协程执行,不定时后再回到此协程
}
func showNumber (i int) {
    fmt.Println(i)
}

// runtime.Goexit()   // 退出此协程,return 是退出此函数

// runtime.GOMAXPROCS()  指定以多少核运算,不写默认单核。runtime.GOMAXPROCS(2) 指定2核cpu运算  

多任务资源竞争:

func main() {
  go printer("hello")
  go printer("world")
  for{}
}
func printer (str string) {
  for _,v:= range str{
     fmt.Printf("%c",v)
     time.Sleep(time.Second)
  }
  fmt.Print("\n")   //  hwoelrllod 打印出来这样,每个协程用一会儿就换另外个
}

// 为了解决多任务资源竞争的问题,需要channel实现

channel数据类型(引用类型),可以用make()创建(用于多个goruntine通讯,内部实现了同步,确保并发安全)

创建一个channel,
make(chan Type)  === make(chan Type, 0 ) 无缓冲的channel 读写阻塞
make(chan Type, cap)  有缓存的channel 无阻塞,满了或者空的才阻塞

channel 数据操作
channel<-  value  // 发送数据到管道
<-channel    //管道取出数据丢弃,
x:= <-channel   // 取出数据赋值给x
x,ok := <-channel  // 功能同上,同时检测通道是否已经关闭或者是否为空

默认情况下,channel收发数据都是阻塞的,除非另一端准备好,同时接收,这就让gorountine同步变得更加简单,不需要显示的lock
var chans = make(chan int) // 创建一个通道channel
func main(){
   go pers("hello")   // 开启新协程
   go pers1("world")  // 开启新协程
   for{}  // 防止主协程退出 
   // world    hello
}
func pers(str string){
  <-chans   // 通道取值,没有则阻塞此通道运行
  Printer(str)
}
func pers1(str string){
  Printer(str)  
  chans <- 666  // 通道传递值。
}
func Printer(str string){
   for _, v := range str{
     fmt.Printf("%c",v)
   }
   fmt.Print("\n")
}

channel 实现同步和数据交互

func main(){
  var cha = make(chan string)
  go func (){
    for i:=0;i<2; i++{
      fmt.Println(i)
    }
    cha<- "oks"
    fmt.Println("子协程结束")
  }()
  str := <- cha
  fmt.Println(str,"主协程结束")
}

无缓冲channel

func main(){
  var cha = make(chan int) //创建无缓冲channel
  go func (){
    for i:=0;i<2; i++{
      cha <- i
      fmt.Println("子协程",i)
    }
    fmt.Println("子协程结束")
  }()
  time.Sleep(time.Second)
  for i:=0;i<2; i++{
    str := <-cha
    fmt.Println("主协程",str)
  }
  fmt.Println("主协程结束")
  /*
  主协程 0
  子协程 0
  子协程 1
  子协程结束
  主协程 1
  主协程结束
   */
}

有缓冲channel

func main(){
  var cha = make(chan int,3) //创建有缓冲channel
  go func (){
    for i:=0;i<5; i++{
      cha <- i
      fmt.Println("子协程",i)
    }
    fmt.Println("子协程结束")
  }()
  time.Sleep(time.Second)
  for i:=0;i<5; i++{
    str := <-cha
    fmt.Println("主协程",str)
  }
  fmt.Println("主协程结束")
  /*
  子协程 0
  子协程 1
  子协程 2  // 一次性存三个,然后等一秒
  主协程 0
  主协程 1
  主协程 2  // 一次性取三个。后面的就不确定了
  主协程 3
  子协程 3
  子协程 4
  子协程结束
  主协程 4
  主协程结束
   */
}

关闭channel

func main(){
  var cha = make(chan int)
  go func (){
     for i:=0 ;i <4; i++{
       cha<- i
     }
     close(cha)  // 关闭channel,能继续读数据,不能存数据
  }()
  time.Sleep(time.Second)
  for num:=range ch{
      fmt.Println("num=",num)
  }  // 遍历channel,关闭后自动退出遍历
  for{
     if v,oks := <-cha; oks ==true{ //v为值,oks为cha是否关闭,关闭为false
        fmt.Println("主协程",v)
     }else{break}
  }
}

单向channel特点和应用

var cha1 chan int   // 正常channel,双向
var cha2 chan<-  float64  // 单项,只能写入
var  cha3  <-chan  int   // 单项,只能读取int数据
双向可以隐式转换为单项channel,反之不能

ch :=make(chan int)  //双向
var  wirteCh chan<-  int = ch // 只能写,不能读
var  readCh <-chan  int = ch // 只能读,不能写
eg:
func Productor(out chan<- int){ // 参数必须为输入channel
   for i:=0 ;i<10; i++ {
     out<- i*i // 写入
   }
   close(out) // 关闭channel
}
func customer(in <-chan int){ // 参数必须为输出channel
   for v := range in{  // 当channel关闭后会自动退出循环
     fmt.Print(v," ")
   }
}
func main(){
   ch := make(chan int)
   go Productor(ch)
   customer(ch)
   /*  0 1 4 9 16 25 36 49 64 81  */
}

定时器Timer 和Ticker

Timer:一次性定时器

// Timer实现延时功能
func main(){
   timer := time.NewTimer(time.Second*4) //创建一个timer,多少秒后会注入到timer的channel里面
   <-timer.C   // 一开始为空值,阻塞程序运行,取到值后继续执行下面
   fmt.Print(timer)
}

// time.Sleep( time.Second *n) 也能延时 n秒

// <- time.After( 2 * time.Second)  // 定时2s,2s后往channel写内容,返回的是一个管道

// 停止定时器 timer.Stop()
timer := time.NewTimer(3 * time.Second)
go  func(){
    <-timer.C
    fmt.Print("xxx")
}
timer.Stop() //停止定时器  导致xxx不能打印

// 重置定时器
timer := time.NewTimer(3* time.Second)
timer.Reset(1 * time.Second)  // 重新设置为1s, 返回值为bool
<-timer.C  //去channel的值

Ticker:周期往channel发送一个事件,而channel接收者可以固定时间间隔从channel里面取

 ticker := time.NewTicker(time.Second*2)
   i :=0
   for{
      i++
      <-ticker.C
      fmt.Println(i)
      if i ==5{
        ticker.Stop()
        break
      }
   }

select 监测channel上的数据流动

每个case语句里必须是一个IO操作,在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句,如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:
如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。
如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去。

func main(){
  // 超时打印
  ch :=make(chan int)
  quit :=make(chan bool)
  go func (){
    for{
      select{
      case num := <-ch:
         fmt.Println(num)
      case <-time.After(5*time.Second): //5秒后添加事件到time的channel事件
         quit <- true  
         fmt.Print("超时quit\n")
      }
    }
  }()
  <-quit
  fmt.Println("主协程退出")
}

select 实现斐波拉切数列

func main() {
    // select 实现斐波拉切数列
    ch := make(chan int)    // 数据传递通道
    quit := make(chan bool) // 退出通道

    // 使用者
    go func() {
        for i := 0; i < 8; i++ {
            num := <-ch
            fmt.Println(num)
        }
        quit <- true
    }()
    //生产者
    feibo(ch, quit)
}
func feibo(ch chan<- int, quit <-chan bool) {
    x, y := 1, 1
    for {
        select {
        case ch <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Print("oks")
            return
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • channel[通道]是golang的一种重要特性,正是因为channel的存在才使得golang不同于其它语言。...
    码洞阅读 2,216评论 0 51
  • channel[通道]是golang的一种重要特性,正是因为channel的存在才使得golang不同于其它语言。...
    码洞阅读 22,423评论 1 53
  • Go入门 Go介绍 部落图鉴之Go:爹好还这么努力? 环境配置 安装 下载源码编译安装 下载相应平台的安装包安装 ...
    齐天大圣李圣杰阅读 4,567评论 0 26
  • 由浅入深剖析 go channel channel 是 golang 中最核心的 feature 之一,因此理解 ...
    不智鱼阅读 59,672评论 4 83
  • 一切都是那么凑巧 你刚好觉得可以接受 而我觉得没有理由拒绝 以为所有事都在顺其自然发展 殊不知 矛盾愈愈剧增 一直...
    pinkzier阅读 812评论 0 0