Go语言并发、锁、channel

2018-10-11  本文已影响0人  AuglyXu

runtime包中常用的方法




互斥锁

    /*
    需求: 定义个打印的函数, 可以逐个的打印传入字符串的每个字符
    在两个协程中调用这个好方法, 并且还要保持输出的内容有序的
     */
  var lock = sync.Mutex{}
  func printer(str string){
  lock.Lock()//添加锁,如果不添加那么可能执行输出hello也可能执行输出world,那么就是无序的
    for _,val := range str{
        fmt.Printf("%c", ch)
        time.Sleep(time.Millisecond * 300)
    }
  lock.Unlock()
  }
func main(){
  go printer("hello")
  go printer("world")
for{
    ;
  }
}

互斥锁的资源抢夺问题

  var lock = sync.Mutex{}
  var buff = [10]int
  func producer(){
    lock.Lock()
    rand.Seed(time.Now().UnixNano())
    for i:=0; i < 10 ;i++{
        num := rand.Intn(100)
        fmt.Println("生产者生产了",num)
        buff[i] = num
        time.Sleep(time.Millisecond * 300)
    }
    lock.Unlock()
  }

 func consumer(){
    lock.Lock()
    for i:=0; i < 10 ;i++{
        buff[i] = num
        fmt.Println("消费者消费到了",num)
    }
    lock.Unlock()
  }

func main() {

    go producer()
    // 我们想要的是, 只有生产者生产了, 我们才能消费
    // 注意点: 在多go程中, 如果生产者生产的太慢, 那么消费者就会消费到错误的数据
    go consumer()
    // 注意点: 看上去通过给生产者以及消费者同时加锁就能解决, 只有生产完了才能消费
    //         但是取决于谁想执行加锁操作, 所以不完美
    for{
        ;
    }
}

管道

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

管道的使用

管道写入数据

var myCh chan int
myCh = make(chan int, 3)
myCh<-666  //写入了一个数据666

管道读取数据

var myCh chan int
myCh = make(chan int, 3)
myCh<-666  //写入了一个数据666
fmt.Println(<-myCh) //读取了666

管道写入和读取的注意点

var myCh chan int
myCh<-666  //报错
var myCh chan int
myCh = make(chan int, 3)
fmt.Println(<-myCh) //报错
var myCh chan int
myCh = make(chan int, 3)
myCh<-1 //写入了一个数据1
myCh<-2 //写入了一个数据2
myCh<-3 //写入了一个数据3
myCh<-4 //报错

管道的关闭

管道的遍历

for range遍历

var myCh chan int
myCh = make(chan int, 3)
myCh<-1 //写入了一个数据1
myCh<-2 //写入了一个数据2
myCh<-3 //写入了一个数据3
close()//管道必须关闭,否则报错
for v := range myChan{
  fmt.Println(v) //先后输出 1 2 3
}

死循环遍历

var myCh chan int
myCh = make(chan int, 3)
myCh<-1 //写入了一个数据1
myCh<-2 //写入了一个数据2
myCh<-3 //写入了一个数据3
close()//管道必须关闭,否则报错
for{
  if value,ok:= <-myChan;ok{
       fmt.Println(v) //先后输出 1 2 3
  }
}

管道的阻塞现象(重点)

利用管道阻塞实现并发串行

  var myChan chan int
  myChan = make(chan int,10)
  func producer(){
    rand.Seed(time.Now().UnixNano())
    for i:=0; i < 10 ;i++{
        num := rand.Intn(100)
        myChan <- num
        fmt.Println("生产者生产了",num)
        time.Sleep(time.Millisecond * 300)
    }
  }
func producer2(){
    rand.Seed(time.Now().UnixNano())
    for i:=0; i < 10 ;i++{
        num := rand.Intn(100)
        myChan <- num
        fmt.Println("生产者生产了",num)
        time.Sleep(time.Millisecond * 300)
    }
  }

 func consumer(){
    for i:=0; i < 10 ;i++{
        num := <-myChan
        fmt.Println("消费者消费到了",num)
    }
  }

func main() {
    go producer()
    go producer2()
    go consumer()
    for{
        ;
    }
}

利用管道阻塞解决最后写死循环保证主线程不挂

    var myCh = make(chan int, 3)
    var exitCh = make(chan bool, 1)
    go func() {
        for i:=0; i<3; i++ {
            fmt.Println("生产了数据", i)
            myCh<-i
        }
        exitCh<-true
    }()
    fmt.Println("exitCh之前的代码")
    <-exitCh // 阻塞
    fmt.Println("exitCh之后的代码")

无缓冲管道

   //在go程中可以只读或者只写,会阻塞
    myCh := make(chan int, 0)
    go func() {
        fmt.Println("123")
        myCh<-998
        //<-myCh
        fmt.Println("abc")
    }()

无缓冲管道解决死循环

//定义一个没有缓冲的管道
    exitCh := make(chan bool)

    go func() {
        for i:= 0; i < 5; i++ {
            myCh<-i
            fmt.Println("生产了", i)
        }
        exitCh<-true
    }()
    //for{
    //  ;
    //}
    //time.Sleep(time.Second)
    <-exitCh

单向管道和双向管道

单向管道作为函数参数
// 定义一个函数模拟生产者
func producer(buff chan<- int)  {
    rand.Seed(time.Now().UnixNano()) // 种随机种子
    for i:=0; i<5;i++  {
        // 产生随机数
        num := rand.Intn(100)
        fmt.Println("生产者生产了", num)
        // 将生产好的数据放入缓冲区
        buff<-num
        //time.Sleep(time.Millisecond * 300)
    }
}

// 定义一个函数模拟消费者
func consumer(buff <-chan int, exitCh chan<- int)  {
    for i:=0; i<5;i++  {
        num := <-buff
        fmt.Println("-------消费者消费到了", num)
    }
    exitCh<-666
}

func main() {
    // 定义一个数组模拟缓冲区
    var buff = make(chan int, 5)
    var exitCh = make(chan int)
    go producer(buff)
    go consumer(buff, exitCh)

    <-exitCh
    fmt.Println("程序结束了")
    //for{
    //  ;
    //}
}

管道是指针类型

    var myCh1 chan int = make(chan int, 5)
    //fmt.Println(myCh1) // 0xc042094000
    //fmt.Printf("%p\n", myCh1) // 0xc042094000
    //fmt.Printf("%p\n", &myCh1) // 0xc042082018
    myCh1<-1
    myCh1<-2

    var myCh2 <-chan int
    myCh2 = myCh1 // 将双向的管道转换成单向的管道
    // 打印单向管道的长度和容量
    fmt.Println("len", len(myCh2), "cap", cap(myCh2))//len 2 cap 5
    fmt.Println(<-myCh2)//1
    // 打印双向管道的长度和容量
    fmt.Println("len", len(myCh1), "cap", cap(myCh1))//len 1 cap 5

select结构

    // 1.创建一个管道
    myCh1 := make(chan int, 5)
    myCh2 := make(chan int, 5)
    exitCh := make(chan bool)

    // 2.开启一个协程生产数据
    go func() {
        //time.Sleep(time.Second * 5) 如果存在这行数据会打印超时了,不存在则会正常消费
        for i := 0; i < 10 ; i++ {
            myCh1<-i
            fmt.Println("生产者1生产了", i)
        }
        close(myCh1)
        exitCh<-true
    }()

go func() {
        time.Sleep(time.Second * 5)
        for i := 0; i < 10 ; i++ {
            myCh2<-i
            fmt.Println("生产者2生产了", i)
        }
        close(myCh2)
    }()

for{
        select {
        case num1 := <-myCh1:
            fmt.Println("------消费者消费了myCh1", num1)
        case <-time.After(3):
            fmt.Println("超时了")
            return
        }
        time.Sleep(time.Millisecond)
    }
    fmt.Println("程序结束了")

定时器

一次性定时器

NewTimer函数

     start := time.Now()
     fmt.Println(start) //打印当前时间
     timer := time.NewTimer(time.Second * 3) // Timer
     fmt.Println(<-timer.C) //打印三秒后的时间

After函数

    start := time.Now()
    fmt.Println(start)
    timer := time.After(time.Second * 3) // Timer.C
    fmt.Println(<-timer)

周期性定时器

    start := time.Now()
    fmt.Println(start)
    ticker := time.NewTicker(time.Second * 2)
    for{
        fmt.Println(<-ticker.C)
    }
上一篇下一篇

猜你喜欢

热点阅读