继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Go(8[Goroutine | Channel | 读写锁 | 异常处理 ])

holdtom
关注TA
已关注
手记 1703
粉丝 240
获赞 991


进程和线程

1. 进程是程序在操作系统中的⼀次执⾏过程,系统进口资源分配和调度的一个独力单位。

2. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是⽐进程更⼩的能独力运行的基本单位。

3. 一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发执行

Goroutine

blob.png

并发和并行

多线程程序在一个核的CPU上运行,就是并发

多线程程序在多个核的CPU上运行,就是并发

设置GO运行的CPU核数

func Test1()  {

    num := runtime.NumCPU()

    //设置程序运行占几个核

    runtime.GOMAXPROCS(num)

    fmt.Println(num)

}

线程同步

sync.WaitGroup

package main

import (

    "fmt"

    "time"

    "sync"

)

var wailtGroup  sync.WaitGroup

//sync 线程同步

func Test2(index int)  {

    for i:=0;i<100;i++{

        time.Sleep(time.Millisecond)

    }

    wailtGroup.Done()

}

func calc()  {

    start :=time.Now().UnixNano()

    //Test2(0)

    for i:=0; i<3;i++{

        go Test2(i)

        wailtGroup.Add(1)

    }

        #当wailtGroup 为0时,就会返回

    wailtGroup.Wait()

    end := time.Now().UnixNano()

    fmt.Printf("finished,cost:%d ms \n",(end - start) / 1000 / 1000)

}

func main()  {

    //Test1()

    calc()

}

不同goroutine之间通信

全局变量和锁同步

Channel

Channel

类似unix中的管道(pipe)

先进先出

线程安全,多个goroutine同时访问,不需要加锁

Channel是有类型的,一个整数的channel只能存放整数

Channel声明:

    var 变量名 chan 类型

                var test chan int

    var test chan string

    var test chan map[string]string

    var test chan stu

    var test chan *stu

Channel初始化:

不带缓冲区:默认就是0,必须有取值,才可以放入,不然就会阻塞!

带缓冲区: 值>0

            使用make进行初始化

            var test chan int

            test =make(chan int,10) 

             var test2 chan string

            test = make(chan string,10)

 Channel基本操作:

1.从channel读取数据

testChan :=make(chan int,10)

var a int

a = <- testChan

2.从channel写数据

testChan:=make(chan int,10)

var a int=  10

testChan <- a

栗子:

1.初级操作

func test()  {

    var intChan chan int = make(chan  int,3)

    go func(){

        fmt.Println("begin input to chan\n")

        intChan <- 20

        intChan <- 20

        intChan <- 20

        fmt.Println("end input to chan\n")

    }()

    result := <- intChan

    fmt.Printf("--result:%d",result)

    time.Sleep(2*time.Second)

}

func main()  {

    test()

}

2.goroutine和channel结合

package main

import (

"fmt"

"time"

)

func main() {

    ch := make(chan string)

    go sendData(ch)

    go getData(ch)

    //加time原因是让2个go去执行, 因为主线程中的代码是比goroutine先执行完毕的

    time.Sleep(100 * time.Second)

}

func sendData(ch chan string) {

        ch <- "Washington"

        ch <- "Tripoli"

        ch <- "London"

        ch <- "Beijing"

        ch <- "Tokio"

}

func getData(ch chan string) {

        var input string

        for {

        input = <-ch

        fmt.Println(input)

        }

}

for循环去遍历chan

package main

import (

"fmt"

"time"

)

func main() {

    ch := make(chan string)

    go sendData(ch)

    go getData(ch)

    time.Sleep(100 * time.Second)

}

func sendData(ch chan string) {

    ch <- "Washington"

    ch <- "Tripoli"

    ch <- "London"

    ch <- "Beijing"

    ch <- "Tokio"

}

func getData(ch chan string) {

    for input := range ch {

        fmt.Println(input)

    }

}

Channel关闭            

使用内置函数close进行关闭,chan关闭后。for range遍历chan中已经存在的元素后结束

func rangegetData(ch chan string)  {

    //另外用法,用来取管道中的数据

    //

    for input:=range ch{

        fmt.Printf("#%v\n",input)

    }

}

使用内置函数close进行关闭,chan关闭后。没有使用for range的写法需要使用,v,ok := <-ch 进行判断chan是否关闭

func getData(ch chan  string)  {

    //死循环

    for {

        input,ok :=<- ch

        //ok是判断管道是否关闭

        if !ok{

            fmt.Printf("管道关闭")

            break

        }

        fmt.Printf("channe_read:%s\n",input)

    }

}

进阶栗子:

func consumer(ch  <-chan string){

   for{

      str,ok := <- ch

      if !ok{

         fmt.Printf("ch is closed!!")

         break

      }

      fmt.Printf("value is %s \n",str)

   }

}

func main(){

var ch chan string = make(chan string)

consumer(ch)}

Channel只读/只写

只读chan声明

var 变量名字 <-chan int

var readChan <-chan int

只写chan声明

var 变量名字 chan<-int

var writeChan chan<-int

Channel Select管理

注意:调度是随机的

一个简单的栗子:

for {

   str := fmt.Sprintf("hello %d",i)

   //select来管理管道

   //调度是随机的,

   select {

   case ch <- str:

   case exit = <-exitChan:

   }

   if exit{

      fmt.Printf("user notify exitedd!!\n")

      break

   }

}

定时器

规定时间后运行代码

package main

import (

   "time"

   "fmt"

)

func run()  {

    t:=time.NewTicker(time.Second * 5)

    //t.C 是一个管道

    for v:=range t.C{

      fmt.Println("hello",v)

    }

}

func main()  {

   run()

}

只运行一次:

package main

import (

   "fmt"

   "time"

)

func main() {

   select {

      case <- time.After(time.Second):

      fmt.Println("after")

   }

}

超时控制 (可以用于检测类似Mysql查询超时等):

package main

import (

   "time"

   "fmt"

)

func queryDB(ch chan  int)  {

   time.Sleep(time.Second * 1000)

   ch <- 100

}

func main()  {

   ch :=make(chan int,5)

   go queryDB(ch)

   //设置主线程运行时间,

   t := time.NewTicker(time.Second * 5 )

   //随机调度

   select {

       //去ch中取,是否有数据,

      case <-ch:

         fmt.Println("reslt")

       

      case <-t.C:

         fmt.Printf("timeout!!!")

   }

}

读写锁:

写锁:sync.Mutex 是互斥锁, 多个线程修改数据的适合,只有一个线程可以修改

读锁:sync.RWMutex 读锁,多个线程同时读一份数据,可以并发的去执行

Go中使用recover

应⽤场景,如果某个goroutine panic了,⽽且这个goroutine⾥⾯没有捕获(recover),那么整个进程就会挂掉。所以,好的习惯是每当go产⽣⼀个goroutine,就需要写下recover

package main

import (

   "time"

   "fmt"

)

func calc()  {

   //捕获异常

   //必须写在最前面

   defer func() {

      error :=recover()

      if error !=nil{

         fmt.Println(error)

      }

   }()

   var p *int

   //p = new(int)

   *p = 100

}

func main()  {

   go calc()

   time.Sleep(time.Second * 2)

   fmt.Println("---")

}

单元测试

©著作权归作者所有:来自51CTO博客作者zhaichaoqun的原创作品,如需转载,请与作者联系,否则将追究法律责任


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP