• Go语言学习教程(十五)


    一、线程休眠

    * Go语言中main()函数为主线程(协程),程序是从上向下执行的

    * 可以通过time包下的Sleep(n)让程序阻塞多少纳秒

       //单位是纳秒,表示阻塞多长时间

       //e9表示10的9次方

       time.Sleep(1e9)

    二、延迟执行

    * 延迟指定时间后执行一次,但是需要注意在触发时程序没有结束

        fmt.Println("开始")

       //2秒后执行匿名函数

       time.AfterFunc(2e9, func() {

          fmt.Println("延迟延迟触发")

       })

       time.Sleep(10e9)//一定要休眠,否则程序结束了

       fmt.Println("结束")

    三、goroutine简介

    * Golang中最迷人的一个优点就是从语言层面就支持并发

    * 在Golang中的goroutine(协程)类似于其他语言的线程

    * 并发和并行

        * 并行(parallelism)指不同的代码片段同时在不同的物理处理器上支持

        * 并发(concurrency)指同时管理多个事情,物理处理器上可能运行某个内容一半后就处理其他事情

        * 在一般看来并发的性能要好于并行.因为计算机的物理资源是固定的,较少的,而程序需要执行的内容是很多的.所以并发是”以较少的资源去去做更多事情”

    * 几种主流并发模型

        * 多线程,每个线程只处理一个请求,只有请求结束后,对应的线程才会接收下一个请求.这种模式在高并发下,性能开销极大.

        * 基于回调的异步IO.在程序运行过程中可能产生大量回调导致维护成本加大,程序执行流程也不便于思维

        * 协程.不需要抢占式调用,可以有效提升线程任务的并发性,弥补了多线程模式的缺点;Golang在语言层面就支持,而其他语言很少支持

    * goroutine的语法

        * 表达式可以是一条语句

        * 表达式也可以是函数,函数返回值即使有,也无效,当函数执行完成此goroutine自动结束

        go 表达式

    四、WaitGroup简介

    * Golang中sync包提供了基本同步基元,如互斥锁等.除了Once和WaitGroup类型, 大部分都只适用于低水平程序线程,高水平同步线程使用channel通信更好一些

    * WaitGroup直译为等待组,其实就是计数器,只要计数器中有内容将一直阻塞

    * 在Golang中WaitGroup存在于sync包中,在sync包中类型都是不应该被拷贝的

    * Go语言标准库中WaitGroup只有三个方法

        * Add(delta int)表示向内部计数器添加增量(delta),其中参数delta可以是负数

        * Done()表示减少WaitGroup计数器的值,应当在程序最后执行.相当于Add(-1)

        * Wait()表示阻塞直到WaitGroup计数器为0

    * 使用WaitGroup可以有效解决goroutine未执行完成而主协程执行完成,导致程序结束的goroutine未执行问题

        var wg sync.WaitGroup

        func main() {

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

              wg.Add(1)

              go demo(i)

           }

       //阻塞,知道WaitGroup队列中所有任务执行结束时自动解除阻塞

       fmt.Println("开始阻塞")

       wg.Wait()

       fmt.Println("任务执行结束,解除阻塞")

    }

    func demo(index int) {

       for i := 1; i <= 5; i++ {

          fmt.Printf("第%d次执行,i的值为:%d\n", index, i)

       }

       wg.Done()

    }

    五、互斥锁

    * Go语言中多个协程操作一个变量时会出现冲突的问题

    * go run -race 可以查看竞争

    * 可以使用sync.Mutex对内容加锁

    * 互斥锁的使用场景

        * 多个goroutine访问同一个函数(代码段)

        * 这个函数操作一个全局变量

        * 为了保证共享变量安全性,值合法性

    * 使用互斥锁模拟售票窗口

    var (

       //票数

       num = 100

       wg  sync.WaitGroup

       //互斥锁

       mu sync.Mutex

    )

    func sellTicker(i int) {

       defer wg.Done()

       for {

          //加锁,多个goroutine互斥

          mu.Lock()

          if num >= 1 {

             fmt.Println("第", i, "个窗口卖了", num)

             num = num - 1

          }

          //解锁

          mu.Unlock()

          if num <= 0 {

             break

          }

          //添加休眠,防止结果可能出现在一个goroutine中

          time.Sleep(time.Duration(rand.Int63n(1000) * 1e6))

       }

    }

    func main() {

       //设置随机数种子

       rand.Seed(time.Now().UnixNano())

       //计算器的起始值和票数相同

       wg.Add(4)

       go sellTicker(1)

       go sellTicker(2)

       go sellTicker(3)

       go sellTicker(4)

       wg.Wait()

       fmt.Println("所有票卖完")

    }

    六、读写锁

    * Go语言中的map不是线程安全的,多个goroutine同时操作会出现错误.

    * RWMutex可以添加多个读锁或一个写锁.读写锁不能同时存在.

        * map在并发下读写就需要结合读写锁完成

        * 互斥锁表示锁的代码同一时间只能有一个goroutine运行,而读写锁表示在锁范围内数据的读写操作

    func main() {

       var rwm sync.RWMutex

       m := make(map[string]string)

       var wg sync.WaitGroup

       wg.Add(10)

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

          go func(j int) {

             //没有锁在map时可能出现问题

             rwm.Lock()

             m["key"+strconv.Itoa(j)] = "value" + strconv.Itoa(j)

             fmt.Println(m)

             rwm.Unlock()

             wg.Done()

          }(i)

       }

       wg.Wait()

       fmt.Println("程序结束")

    }

    一、channel

    * 线程通信在每个编程语言中都是重难点,在Golang中提供了语言级别的goroutine之间通信:channel

    * channel不同的翻译资料叫法不一样.常见的几种叫法

        * 管道

        * 信道

        * 通道

    * channel是进程内的通信方式,每个channel只能传递一个类型的值.这个类型需要在声明channel时指定

    * channel在Golang中主要的两个作用

        * 同步

        * 通信

    * Go语言中channel的关键字是chan

    * 声明channel的语法

    var 名称 chan 类型

    var 名称 chan <- 类型 //只写

    var 名称 <- chan 类型//只读

    名称:=make(chan int) //无缓存channel

    名称:=make(chan int,0)//无缓存channel

    名称:=make(chan int,100)//有缓存channel

    * 操作channel的语法:(假设定义一个channel名称为ch)

    ch <- 值 //向ch中添加一个值

    <- ch //从ch中取出一个值

    a:=<-ch //从ch中取出一个值并赋值给a

    a,b:=<-ch//从ch中取出一个值赋值给a,如果ch已经关闭或ch中没有值,b为false

    * 简单无缓存通道代码示例

        * 此代码中如果没有从channel中取值c,d=<-ch语句,程序结束时go func并没有执行

        * 下面代码示例演示了同步操作,类似与WaitGroup功能,保证程序结束时goroutine已经执行完成

        * 向goroutine中添加内容的代码会阻塞goroutine执行,所以要把ch<-1放入到goroutine有效代码最后一行

        * 无论是向channel存数据还是取数据都会阻塞

        * close(channel)关闭channel,关闭后只读不可写

    func main() {

       ch := make(chan int)

       go func() {

          fmt.Println("进入goroutine")

          // 添加一个内容后控制台输出:1 true

          //ch<-1

          //关闭ch控制台输出:0 false

          close(ch)

       }()

       c, d := <-ch

       fmt.Println(c, d)

       fmt.Println("程序执行结束")

    }

    * 使用channel实现goroutine之间通信

        * channel其实就是消息通信机制实现方案,在Golang中没有使用共享内存完成线程通信,而是使用channel实现goroutine之间通信

    func main() {

       //用于goroutine之间传递数据

       ch := make(chan string)

       //用于控制程序执行

       ch2 := make(chan string)

       go func() {

          fmt.Println("执行第一个goroutine,等待第二个goroutine传递数据")

          content := <-ch

          fmt.Println("接收到的数据为:", content)

          ch2 <- "第一个"

       }()

       go func() {

          fmt.Println("进入到第二个,开始传递数据")

          ch <- "内容随意"

          close(ch)

          fmt.Println("发送数据完成")

          ch2 <- "第二个"

       }()

       result1 := <-ch2

       fmt.Println(result1, "执行完成")

       result2 := <-ch2

       fmt.Println(result2, "执行完成")

       fmt.Println("程序执行结束")

    }

    * 可以使用for range获取channel中内容

        * 不需要确定channel中数据个数

    func main() {

       ch:=make(chan string)

       ch2:=make(chan int)

       go func() {

          for i:=97;i<97+26;i++{

             ch <- strconv.Itoa(i)

          }

          ch2<-1

       }()

       go func() {

          for c := range ch{

             fmt.Println("取出来的",c)

          }

       }()

       <-ch2

       fmt.Println("程序结束")

    }

    * channel是安全的.多个goroutine同时操作时,同一时间只能有一个goroutine存取数据

    func main() {

       ch := make(chan int)

       for i := 1; i < 5; i++ {

          go func(j int) {

             fmt.Println(j, "开始")

             ch <- j

             fmt.Println(j, "结束")

          }(i)

       }

       for j := 1; j < 5; j++ {

          time.Sleep(2 * time.Second)

          <-ch

       }

    }

  • 相关阅读:
    迅雷超级会员和白金会员怎么买最便宜多少钱一年多少钱一个月
    咳嗽检测深度神经网络算法
    C++内存检查
    动手学深度学习(Pytorch版)代码实践 -深度学习基础-07多层感知机基础版
    用c++写平均分
    不会Python迟早失业?Python何以成为找工作必备技能(资料下载)
    问题 D: Wall Clocks
    react18 安装 react-activation 后,依赖报错,解决办法
    基于JAVASketch2Mod网站计算机毕业设计源码+系统+lw文档+部署
    2022年最新甘肃建筑八大员(材料员)模拟考试试题及答案
  • 原文地址:https://blog.csdn.net/qq_40652101/article/details/125595894