go语言之并发、信道、select、mutex、异常处理

一、并发和并行

  并发:同一时间段多个任务在执行(单个CPU执行多个任务)。go使用go协程(goroutine)和信道(channel)来处理并发

  并行:同一时刻多个任务在执行(多个cpu支持)

 

  1、Goroutine ---->协程

    goroutine--->协程---2kb大小,100

    线程----》几个m大小

    go协程会复用线程

    goroutine之间通信,通过信道channel通信

    go推崇用信道通信,而不推崇用共享变量通信(锁,死锁)

    2.1、启动

复制代码
package main

import (
    "fmt"
    "time"
)

//启动一个goroutine

func test()  {
    fmt.Println("go go go")
}
func main() {
    fmt.Println("主线程开始执行")
    go test()
    go test()
    go test()
    go test()
    go test()                           //写几个代表开几个goroutine
    time.Sleep(1*time.Second)          //此时等待了才会打印go go go
    fmt.Println("主线程结束执行")
    //go语言中,主线程不会等待goroutine执行完成,要等待它结束需要自己处理
}

//循环匿名函数开goroutine
//func main() {
//    fmt.Println("主线程开始执行")
//    for i:=0;i<10;i++ {
//        go func(){
//            fmt.Println("go go go ")
//        }()
//    }                             //此时开了10个goroutine,前面必须加go关键字
//    time.Sleep(1*time.Second)
//    fmt.Println("主线程结束执行")
//    //go语言中,主线程不会等待goroutine执行完成,要等待它结束需要自己处理
//}
复制代码

  

   2.2、GMP模型

    G:开的gorputine

    M:当做操作系统真正的线程,实际上是用户线程

    P:professor:现在版本默认情况是cpu核数(可当做cpu)

    

    用户线程,操作系统线程

    Python中,开的线程开出用户线程,用户线程跟操作系统线程1:1的对应关系

    某些语言中,用户线程和操作系统线程是n:1的关系

    go语言中,用户线程和操作系统线程是n:m的关系

    

    3.2例子

复制代码
package main

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

func main() {
    //设置P的大小,认为是cpu核数即可
    runtime.GOMAXPROCS(1)
    
    fmt.Println("主线程开始执行")
    go func() {
        for  {
            fmt.Println("xxxx")

        }
    }()
    //for i:=0;i<10;i++ {
    //    go func(){
    //        for  {
    //            fmt.Println("我是死循环")
    //        }
    //    }()
    //}
    time.Sleep(10*time.Second)
    fmt.Println("主线程结束执行")
}
复制代码

 

二、信道

  不同goroutne之间通信,通过信道channel实现

  1、信道创建

复制代码
package main

import (
    "fmt"
    "time"
)

func main() {
    //1 定义channel
    var c chan int
    //2 信道的零值(引用类型,空值为nil,当做参数传递时,不需要取地址,改的就是原来的,需要初始化再使用)
    fmt.Println(c)
    //3 信道初始化
    c=make(chan int)  //数字暂时先不关注
    //4 信道的放值  (注意赋值和放值)
    //c<-1
    //c=12  //赋值报错
    
    //5 信道取值
    //<-c
    
    //6 取出来赋值给一个变量 int
    //var a int
    //a=<-c
    //a:=<-c

    //7 信道默认不管放值还是取值,都是阻塞的
    //c是引用类型
    go test1(c)             //这里取值,

    a:=<-c  //阻塞   不但实现了两条协程之间通信,还实现了等待协程执行结束。取到值后就执行了,取不到值就阻塞
    fmt.Println(a)
}

func test1(a chan int)  {
    fmt.Println("go go go ")
    time.Sleep(1*time.Second)
    //往信道中放一个值
    a<-10 //阻塞,此时这里放值
}
复制代码

  2、小例子

复制代码
package main

//程序有一个数中 每一位的平方和与立方和,然后把平方和与立方和相加并打印出来
import (
    "fmt"
    "time"
)

func calcSquares(number int, squareop chan int) {
    sum := 0  //总和
    for number != 0 {
        digit := number % 10   //589对10取余数,9   8   5
        sum += digit * digit  //sum=9*9   8*8     5*5
        number /= 10         //num=58    5       0
    }
    time.Sleep(2*time.Second)
    squareop <- sum                //上面计算值出来后,放到信道里,就阻塞到这里
}

func calcCubes(number int, cubeop chan int) {
    sum := 0
    for number != 0 {
        digit := number % 10
        sum += digit * digit * digit
        number /= 10
    }
    time.Sleep(1*time.Second)
    cubeop <- sum
}

//线性执行的情况
//func calcSquares(number int, squareop chan int) int{
//    sum := 0  //总和
//    for number != 0 {
//        digit := number % 10   //589对10取余数,9   8   5
//        sum += digit * digit  //sum=9*9   8*8     5*5
//        number /= 10         //num=58    5       0
//    }
//    time.Sleep(2*time.Second)
//    return sum                           此时就是返回值
//}
//
//func calcCubes(number int, cubeop chan int) int{
//    sum := 0
//    for number != 0 {
//        digit := number % 10
//        sum += digit * digit * digit
//        number /= 10
//    }
//    time.Sleep(2*time.Second)
//    return sum
//}

func main() {
    ctime:=time.Now().Unix()            //取结束前的时间
    fmt.Println(ctime)
    number := 589                      //给定一个值
    sqrch := make(chan int)            //因为是引用类型
    cubech := make(chan int)
    //num1:=calcSquares(number, sqrch)
    //num2:=calcCubes(number, cubech)
    go calcSquares(number, sqrch)         //开启goroutine
    go calcCubes(number, cubech)
    squares, cubes := <-sqrch, <-cubech      //传值
    //squares:= <-sqrch
    //cubes:=<-cubech
    fmt.Println("Final output", squares + cubes)
    ttime:=time.Now().Unix()
    fmt.Println(ttime)
    fmt.Println(ttime-ctime)
}
//最耗时的一个执行完就整个程序执行完
复制代码

 

  3、信道关闭、循环和死锁

复制代码
package main

import "fmt"

//信道的死锁现象,默认都是阻塞的,一旦有一个放,没有人取  或者一个人取,没有人放,就会出现死锁
//func main() {
//    //var c chan int =make(chan int )

//    //c<-1  //其实放不进去,阻塞在这,就死锁
//    //<-c     //没有,取不到,阻塞在这,就死锁

//}


//单向信道
//func sendData(sendch chan<- int) {          //这里放置的箭头<-代表只写信道
//    sendch <- 10
//}
//
//func main() {
//    //sendch := make(chan<- int)   //定义了一个只写信道

//    sendch := make(chan int)   //定义了一个可读可写信道
//    go sendData(sendch)        //传到函数中转成只写信道,在goroutine中,只负责写,不能往外读,主协程读
//    fmt.Println(<-sendch)   //只写信道一旦读,就有问题
//}



///3 关闭信道
//func main() {
//    sendch := make(chan int)
//    //关闭信道
//    //close(sendch)
//
//    //信道可以用for循环循环
//}


// 信道关闭close(sendch) ,for循环循环信道,如果不关闭会报死锁,如果关闭了,放不进去,循环结束
func producer(ch chan int) {
    for i := 0; i < 100; i++ {
        fmt.Println("放入了",i)
        ch <- i                      //开始阻塞,后面放值就不阻塞了
    }
    close(ch)
}
func main() {
    ch := make(chan int)
    go producer(ch)               //开启goroutine
    for v := range ch {              //此时循环放值
        fmt.Println("Received ",v)
    }
}
复制代码

 

  4、缓冲信道

    只在缓冲已满的情况,才会阻塞向缓冲信道,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据

复制代码
package main

import (
    "fmt"
    "sync"
    "time"
)

//func main() {
//    var c chan int =make(chan int,6)    //无缓冲信道数字是0,数字是缓冲大小,为6代表可以放6个
//    c<-1
//    c<-2
//    c<-3
//    c<-4
//    c<-5
//    c<-6
//    //c<-7  //死锁

//    <-c
//    <-c
//    //<-c
//    //<-c
//    //<-c
//    //<-c
//    //<-c // 取空了,死锁(一个goroutine中会出现)


// 2 长度 vs 容量
//    //fmt.Println(len(c))  //目前放了多少
//    //fmt.Println(cap(c)) //可以最多放多少
//}

//3 WaitGroup  等待所有goroutine执行完成
func process1(i int,wg *sync.WaitGroup)  {
    fmt.Println("started Goroutine ", i)
    time.Sleep(2 * time.Second)
    fmt.Printf("Goroutine %d ended\n", i)
    //一旦有一个完成,减一
    wg.Done()
}

func main() {
    var wg sync.WaitGroup   //定义一个WaitGroup变量。没有初始化,值类型,当做参数传递,需要取地址
    //fmt.Println(wg)
    for i:=0;i<10;i++ {
        wg.Add(1)          //启动一个goroutine,add加1
        go process1(i,&wg)
    }
    wg.Wait() // 一直阻塞在这,直到调用了10个done,计数器减到零
}
复制代码

 

三、select使用

  谁先取到数据,就先执行谁。随机选取

复制代码
package main

import (
    "fmt"
    "time"
)

//func server1(ch chan string) {
//    time.Sleep(6 * time.Second)
//    ch <- "from server1"
//}
//
//
//func server2(ch chan string) {
//    time.Sleep(3 * time.Second)
//    ch <- "from server2"
//
//}
//
//func main() {
//    output1 := make(chan string)
//    output2 := make(chan string)
//    //开启两个协程执行server
//    go server2(output1)
//    go server2(output2)
//    select {
//    case s1 := <-output1:
//        fmt.Println(s1,"ddddd")
//    case s2 := <-output2:
//        fmt.Println(s2,"yyyy")
//    }
//}

//func process(ch chan string) {
//    time.Sleep(10500 * time.Millisecond)
//    ch <- "process successful"
//}
//
//func main() {
//    ch := make(chan string)
//    go process(ch)
//    for {
//        time.Sleep(1000 * time.Millisecond)
//        select {
//        case v := <-ch:
//            fmt.Println("received value: ", v)
//            return
//        default:
//            // 可以干其他事,模拟非阻塞式io
//            fmt.Println("no value received")
//        }
//    }
//
//}

//死锁
//func main() {
//    ch := make(chan string)
//    select {
//    case <-ch:
//    }
//}

// 随机选取
func server1(ch chan string) {
    ch <- "from server1"
}
func server2(ch chan string) {
    ch <- "from server2"

}
func main() {
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    time.Sleep(1 * time.Second)
    select {                           //谁先回来就执行谁
    case s1 := <-output1:
        fmt.Println(s1)
    case s2 := <-output2:
        fmt.Println(s2)
    }
}
复制代码

 

四、mutex

  使用锁的场景:多个goroutine通过共享内存在实现数据通信

  临界区:当程序并发地运行时,多个 [Go 协程]同时修改共享资源的代码。这些修改共享资源的代码称为临界区。

  如果在任意时刻只允许一个 Go 协程访问临界区,那么就可以避免竞态条件。而使用 Mutex 可以达到这个目的

复制代码
package main

import (
    "fmt"
    "sync"
)

//var x  = 0  //全局,各个goroutine都可以拿到并且操作
//func increment(wg *sync.WaitGroup,m *sync.Mutex) {
//    m.Lock()          //加锁
//    x = x + 1
//    m.Unlock()       //解锁
//    wg.Done()
//}
//func main() {
//    var w sync.WaitGroup
//    var m sync.Mutex              //是个值类型,函数传递需要传地址,所以前面加*
//    fmt.Println(m)
//    for i := 0; i < 1000; i++ {
//        w.Add(1)
//        go increment(&w,&m)
//    }
//    w.Wait()
//    fmt.Println("final value of x", x)
//}

// 通过信道来做
var x  = 0
func increment(wg *sync.WaitGroup, ch chan bool) {
    ch <- true  // 缓冲信道放满了,就会阻塞
    x = x + 1
    <- ch
    wg.Done()
}
func main() {
    var w sync.WaitGroup
    ch := make(chan bool, 1)  //定义了一个有缓存大小为1的信道
    for i := 0; i < 1000; i++ {
        w.Add(1)
        go increment(&w, ch)
    }
    w.Wait()
    fmt.Println("final value of x", x)
}


// 不同goroutine之间传递数据:共享变量,通过信道
// 如果是修改共享变量,建议加锁
// 如果是协程之间通信,用信道
复制代码

 

五、异常处理

  与Python的异常捕获对比

  defer:延迟执行,并且即便程序出现严重错误,也会执行

  panic:主动抛出异常 raise

  recover:恢复程序,继续执行

复制代码
package main

import (
    "fmt"
    "os"
)

//func main() {
//    defer fmt.Println("我最后执行")            //注册一下,并不执行,等main函数执行完了以后,从下往上执行defer定义的东西
//    defer fmt.Println("我倒数第二个打印")
//    fmt.Println("我先执行")
//    //var a []int
//    //fmt.Println(a[10])
//    panic("我出错了")
//    fmt.Println("ccccc")
//
//    //假设打开一个文件
//    //f:=open()
//    //defer f.close()
//    //
//    //出错了
//    //
//
//}


//例子
//func f1(){
//    fmt.Println("f1 f1")
//}
//func f2(){
//    defer func() {   //这个匿名函数永远会执行
//        //error:=recover()  //恢复程序继续执行
//        //fmt.Println(error) //如果没有错误,执行recover会返回nil    如果有错误,,执行recover会放错误信息
//        if error:=recover();error!=nil{
//            //表示出错了,打印一下错误信息,程序恢复了,继续执行
//            fmt.Println(error)
//        }
//        // 相当于finally
//        fmt.Println("我永远会执行,不管是否出错")
//    }()
//    fmt.Println("f2 f2")
//    //panic("主动抛出错误")
//}
//func f3(){
//    fmt.Println("f3 f3")
//}
//
//func main() {
//    //捕获异常,处理异常,让程序继续运行
//
//    f1()
//
//    f2()
//    f3()
//}

/*
try:
    可能会错误的代码
except Exception as e:
    print(e)
finally:
    无论是否出错,都会执行
*/
/*现在这么写
defer func() {
    if error:=recover();error!=nil{
        //except的东西
        fmt.Println(error)
    }
    //相当于finally,无论是否出错,都会执行
}()
可能会错误的代码
*/


// go的错误处理
func main() {
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}
复制代码

 

posted @   新入世界的小白  阅读(147)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示