6.并发

1. goroutine

go关键字实现的最简单的并发, 注意程序会在main函数结束时退出程序,并不会等待其他goroutin结束。

package main

import (
    "fmt"
    "time"
)

func Add(x, y int)  {
    z := x + y
    fmt.Println(z)
}

func main() {
    x := 1
    y := 1
    for i:=0; i<10 ;i++  {
        go Add(x, y)
    }

    time.Sleep(4)
}

2.并发通信

并发通信通过 共享数据 和 消息来实现

package main
import (
    "fmt"
    "runtime"
    "sync"
)
var counter = 0
func Add(lock *sync.Mutex)  {
    lock.Lock()
    counter += 1
    lock.Unlock()
}
func main() {
    lock := &sync.Mutex{}
    for i:=0 ; i<10 ; i++  {
        go Add(lock)
    }
    for {
        lock.Lock()
        c := counter
        lock.Unlock()
        runtime.Gosched()   // 让出cpu的时间片,程序遇到goshed就好让出cpu让给其他goroutine执行
        if c >= 10{
            break
        }
    }
    fmt.Println(counter)
}

3. chanel

package main

import "fmt"

func Count(count *int, ch chan int)  {
    *count = *count + 1
    fmt.Println(*count)
    ch <- 1    // 想channel 中写入数据,在channel中的数据被读取之前,这个操作是阻塞的
}

func main() {
    count := 0
    chs := make([]chan int, 5)
    for i:=0;i<5;i++{
        chs[i] = make(chan int)
        go Count(&count,chs[i])
    }
    for _, ch := range chs{
        <- ch   // 从channel中读取数据,在数据被写入之前,这个操作是
    }
}

3.1 基本语法

// 声明
var chanName chan ElementType
//在类型之前加了一个chan关键字

var ch chan int

var m map[string] chan bool

// 定义
ch = make(chan int)   // 初始化一个int型的channel


//channel 的读写
ch <- value // 将一个数据发送至channel, 写入数据会产生阻塞,知道其他grountine从channel中将数据读走
varlue = <-ch  // 从channel读取数据,如果channel中没数据也会导致阻塞,知道有数据写入
package main

import "fmt"

func Counsumer(ch1, ch2 chan int)  {
    var value int
    for {
        value = <-ch1
        if value >= 100{
            ch2 <- 0
        }
        fmt.Println("消费了", value)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go Counsumer(ch1, ch2)
    var i int = 0
    for i < 100{
        i += 1
        ch1 <- i
        fmt.Println("生产了", i)
    }
    <- ch2
}

3.2 select

select 语法与switch相似,只不过每一个case条件必须是一个channel操作。

package main
import "fmt"
func main() {
    ch := make(chan int, 1)
    j := 0
    for j < 100{
        j += 1
        select {
        case ch <- 0:
        case ch <- 1:
        }
        i := <-ch
        fmt.Println(i)
    }
}

执行上面代码会发现打印的数字有时为1有时为0。这与select的机制有关,它是随机的。

3.3 缓冲机制

package main

import "fmt"

func Consumer(ch chan int)  {
    for{
        value := <-ch
        fmt.Println("消费了", value)
    }
}

func main() {
    ch := make(chan int, 100)   // 构造channel的时候可以指定channel的缓存大小
    i := 0                                 // 设置缓存大小后,如果chan没满则生产环节不再阻塞

    go Consumer(ch)

    for i < 100{
        i += 1
        print("生产", i, "\n")
        ch <- i
    }
}

带缓冲区的channel还可以这样读

package main

import (
    "fmt"
)

func Consumer(ch, ch2 chan int)  {
    Loop1:
    for   {
        //value := <-ch
        for value := range ch{
            fmt.Println("消费了", value)
            if value >= 100{
                break Loop1
            }
        }
    }
    fmt.Println("lalala")
    ch2 <- 0
}

func main() {
    ch := make(chan int, 100)
    ch2 := make(chan int)
    i := 0
    go Consumer(ch, ch2)
    for i < 100{
        i += 1
        print("生产", i)
        ch <- i
    }
    <-ch2
}

3.4超时机制

package main

import (
    "fmt"
    "time"
)


func main() {
    timeout := make(chan int, 1)
    func() {
        time.Sleep(1)
        timeout <- 1
    }()
    ch := make(chan int, 1)
    ch <- 1
    t := time.Now()
    select{
    case <-ch:
        fmt.Println("从ch中读到数据")
    case <-timeout:
        fmt.Println("从timeout读取数据")
    }
    fmt.Println(time.Now().Sub(t))
}

 3.5 channel的传递                                                                                                                                                             

chanel本身也是原生类型,与map之类的类型一样,channel定义之后也可以通过channel传递

package main

type Pipe struct {
    value int
    handler func(i int) int
    next chan int
}

func handle(queue chan *Pipe)  {
    for data := range queue{
        data.next <- data.handler(data.value)
    }    
}

func add(i int)int  {
    return i + 1
}

func main() {
    var p1 Pipe = Pipe{1, add, make(chan int,1)}
}

3.6 单向channel

var ch1 chan int   // 正常的channel
var ch2 chan<- float64  // ch2是单向channel, 只能用于写float64数据
var ch3 <-chan int        // 只能用于读取int数据


ch4 := make(chan int)
ch5 := <-chan int(ch4)    // 将ch4转换为单向读取channel
ch6 := chan<- int(ch4)    // 将ch4转换为单向写channel


// 用法
func Parse(ch <-chan int) {
    for value := range ch{    // 在这个函数中只能对ch进行读操作
        fmt.Println(value)
    }
}
package main

import (
    "fmt"
    "time"
)

func read(ch <-chan int)  {
    value := <-ch
    fmt.Println(value)
}

func main() {
    ch4 := make(chan int, 1)
    go read(ch4)
    ch4 <- 1
    time.Sleep(1e9)
}

3.7 关闭channel

close(ch)

3.8 出让时间片

runtime.Gosched()
// 如果要精细的控制goroutine的行为,就必须深入的了解runtime包提供的具体功能

3.9 同步

3.9.1同步锁:

  sync.Mutex 

  sync.RWmutex  单写多读模型,一个goroutine获取读锁之后,只限制写,其他goroutine仍可以读。而写锁会组织所有goroutine获取读锁和写锁

  Lock 写锁

  RLock 读锁

var l sync.Mutex
func foo() {
    l.Lock()
    defer l.Unlock()  
}

3.9.2全局唯一性操作

先来看一段代码

package main

import "fmt"

var done bool = false

func setup()  {
    fmt.Println("hello, world")
    done = true
}

func doprint() {
    if !done{
        setup()
    }
}

这段代码的目的是保证setup函数只被执行一次。但是细看还是会有问题,因为setup并不是一个原子性操作, 这种写法可能会导致setup函数被多次调用。

    

猜你喜欢

转载自www.cnblogs.com/zhangjian0092/p/12370565.html