• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
正在输入>
博客园    首页    新随笔    联系   管理    订阅  订阅

GO学习笔记(10)

并发编程

  1.  goroutine: 在函数前加一个关键字go,就为这个函数创建了一个线程

package main

import (
    "fmt"
    "time"
)

func hello(i int) {
    fmt.Println("i = ", i)
    time.Sleep(time.Second)
}

// 普通调用
func demo1() {
    start := time.Now().Unix()
    for i := 0; i < 10; i++ {
        hello(i)
    }
    time.Sleep(time.Second * 10)
    end := time.Now().Unix()
    fmt.Println(end - start) // 耗时20s, i顺序输出
}

// 开启线程,并发调用
func demo2() {
    start := time.Now().Unix()
    for i := 0; i < 10; i++ {
        go hello(i)
    }
    time.Sleep(time.Second * 10)
    end := time.Now().Unix()
    fmt.Println(end - start) // 耗时10s, i每次输出顺序不同
}

func hello1() {
    i := 0
    for {
        i++
        fmt.Println("hello1: i = ", i)
        time.Sleep(time.Second)
    }
}
// 当主线程退出,其他任务则不再执行
func demo3() {
    go hello1()
    i := 0
    for {
        i++
        fmt.Println("demo3: i = ", i)
        time.Sleep(time.Second)
        if i == 2 {
            break
        }
    }
    /**
    demo3: i =  1
    hello1: i =  1
    hello1: i =  2
    demo3: i =  2
    hello1: i =  3
     */
}

  2. runtime: 

package main

import (
    "fmt"
    "runtime"
    "time"
)

// runtime.Gosched() 让出CPU时间片,重新等待安排任务
func goSched() {
    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() 退出当前协程
func goExit() {
    go func() {
        defer fmt.Println("A.defer")
        func() {
            defer fmt.Println("B.defer")
            // 结束协程
            runtime.Goexit()
            defer fmt.Println("C.defer")
            fmt.Println("B")
        }()
        fmt.Println("A")
    }()
    for {

    }
}

// runtime.GOMAXPROCS 同时执行Go代码时使用OS线程数量
func a() {
    for i := 1; i < 10; i++ {
        fmt.Println("A:", i)
    }
}
func b() {
    for i := 1; i < 10; i++ {
        fmt.Println("B:", i)
    }
}
func goMaxProcs()  {
    runtime.GOMAXPROCS(2)
    go a()
    go b()
    time.Sleep(time.Second)
}
func main() {
    goSched()
    goExit()
    goMaxProcs()
}

  3. channel

package main

import "fmt"

// 无缓冲通道 (同步通道)
func demo4() {
    ch1 := make(chan int)
    ch1 <- 10
    fmt.Println("发送完成:", ch1)
    // fatal error: all goroutines are asleep - deadlock!
    // 无缓冲的通道只有在有人接收值的时候才能发送值
}

// 解决办法
func recv(c chan int) {
    x := <-c
    fmt.Printf("接受成功: %v, %T \n", x, x) // 接受成功: 101, int
}
func demo5() {
    c := make(chan int)
    go recv(c)
    c <- 101
    fmt.Printf("发送完成:%T \n", c) // 发送完成:chan int
}

// 有缓冲的通道
func demo6() {
    c := make(chan int, 2)
    c <- 10
    c <- 11
    //c <- 12    // 因为通道的容量为2,所以在没有接受前,不能放第三个值
    fmt.Printf("发送完成:%T \n", c) // 发送完成:chan int
}

// 关闭通道 close()
func demo7() {
    c := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            c <- i
        }
        close(c)
    }()
    for {
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }
    fmt.Println("main结束")
}

// 判断通道是否关闭
func demo8() {
    // 在demo7中,我们使用 data, ok := <-c; 通道关闭后再取值ok=false
    // 但是通常推荐使用for...range 来判断; 通道关闭后会退出for range循环
    c := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            c <- i * i
        }
        close(c)
    }()
    for i := range c { // 通道关闭后会退出for range循环
        fmt.Println(i)
    }
}

// 单向通道
func counter(out chan<- int) { // out 只能发送: chan<- int
    for i := 0; i < 10; i++ {
        out <- i
    }
    close(out)
}
func squarer(out chan<- int, in <-chan int) { // out 只能发送: chan<- int; in 只能接收: <-chan int
    for i := range in {
        out <- i * i
    }
    close(out)
}
func printer(in <-chan int) { // in 只能接收: <-chan int
    for i := range in {
        fmt.Println(i)
    }
}
func demo9() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go counter(ch1)
    go squarer(ch2, ch1)
    printer(ch2)
}

func main() {
    demo4()
    demo5()
    demo6()
    demo7()
    demo8()
    demo9()
}

 

   4. goroutine池: 生产者消费者模型, 可以有效控制goroutine数量

  教程的例子很不错

package main

import (
    "fmt"
    "math/rand"
)

type Job struct {
    // id
    Id int
    // 需要计算的随机数
    RandNum int
}

type Result struct {
    // 这里必须传对象实例
    job *Job
    // 求和
    sum int
}

func main() {
    // 需要2个管道
    // 1.job管道
    jobChan := make(chan *Job, 128)
    // 2.结果管道
    resultChan := make(chan *Result, 128)
    // 3.创建工作池
    createPool(64, jobChan, resultChan)
    // 4.开个打印的协程
    go func(resultChan chan *Result) {
        // 遍历结果管道打印
        for result := range resultChan {
            fmt.Printf("job id:%v randnum:%v result:%d\n", result.job.Id,
                result.job.RandNum, result.sum)
        }
    }(resultChan)
    var id int
    // 循环创建job,输入到管道
    for {
        id++
        // 生成随机数
        r_num := rand.Int()
        job := &Job{
            Id:      id,
            RandNum: r_num,
        }
        jobChan <- job
    }
}

// 创建工作池
// 参数1:开几个协程
func createPool(num int, jobChan chan *Job, resultChan chan *Result) {
    // 根据开协程个数,去跑运行
    for i := 0; i < num; i++ {
        go func(jobChan chan *Job, resultChan chan *Result) {
            // 执行运算
            // 遍历job管道所有数据,进行相加
            for job := range jobChan {
                // 随机数接过来
                r_num := job.RandNum
                // 随机数每一位相加
                // 定义返回值
                var sum int
                for r_num != 0 {
                    tmp := r_num % 10
                    sum += tmp
                    r_num /= 10
                }
                // 想要的结果是Result
                r := &Result{
                    job: job,
                    sum: sum,
                }
                //运算结果扔到管道
                resultChan <- r
            }
        }(jobChan, resultChan)
    }
}

   5. select 多路复用:

  前面有讲到select可以做条件语句使用, 每一个case都是一个通信操作

  其实它的功能就是同时响应多个通道的操作

// 同时相应多个通信
// 其中一个通信操作完成,就执行该case分支
func demo01() {
    c1 := make(chan int)
    c2 := make(chan int)

    go func() {
        time.Sleep(time.Second)
        c1 <- 1
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- 2
    }()

    select {
    case r1 := <-c1:
        fmt.Println(r1)
    case r2 := <-c2:
        fmt.Println(r2)
    }
}

// 多个通信同时完成,则随机选择一个执行
func demo02() {
c1 := make(chan int)
c2 := make(chan int)

go func() {
c1 <- 1
}()
go func() {
c2 <- 2
}()

select {
case r1 := <-c1:
fmt.Println(r1)
case r2 := <-c2:
fmt.Println(r2)
}
}
 

// select还可以用来判断管道是否存满
func demo03() {
c1 := make(chan int)

go func(c chan int) {
for {
select {
case c <- 1:
fmt.Println("c <- 1")
default:
fmt.Println("channel full")
}
time.Sleep(time.Second)
}
}(c1)
for r := range c1 {
fmt.Println("res:", r)
time.Sleep(time.Second)
}
}

 

  6. mutex: 多个goroutine同时操作一个资源, 就会发生竞态问题


var x int64
var wg sync.WaitGroup
var lock sync.Mutex    // 互斥锁

func add() {
for i := 0; i < 5000; i++ { x = x + 1 } wg.Done() } func demo003() { wg.Add(2) go add() go add() wg.Wait() fmt.Println(x) // 结果不是我们想要的 }


// go 使用互斥锁可以解决这个问题
func add1()  {
for i := 0; i < 5000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func demo04() {
wg.Add(2)
go add1()
go add1()
wg.Wait()
fmt.Println(x)  // 结果是10000
}

// 读写互斥锁: 用于读多写少
// 读的时候,其他线程都能读,但是不能写; 写的时候其他线程不能读写
func write() {
rwlock.Lock() // 写锁
x = x+1
time.Sleep(time.Second)
rwlock.Unlock()
wg.Done()
}
func read() {
rwlock.Lock()// 读锁
time.Sleep(time.Microsecond)
rwlock.Unlock()
wg.Done()
}
func demo05() {
start := time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}

for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}

wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}

 

posted @ 2020-08-12 11:33  正在输入>  阅读(178)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3