【GO语言基础】Go基础语法——协程

Go使用指南–基础语法

前言

进程、线程 和 协程 之间概念的区别, 对于 进程、线程,都是有内核进行调度,有 CPU 时间片的概念,进行 抢占式调度(有多种调度算法)。而对于 协程(用户级线程),这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的 CPU 控制权切换到其他进程/线程,通常只能进行 协作式调度,需要协程自己主动把控制权转让出去之后,其他协程才能被执行到。

​ 由于Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU § 转让出去,让其他 goroutine 能被调度并执行,也就是 Golang 从语言层面支持了协程。Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go关键字就可创建一个协程。

​ 协程的优势:协程在切换开销方面,goroutine 远比线程小消耗小。且协程默认占用内存远比 Java 、C 的线程少。

1. 协程定义,go关键字的使用

如下定义add方法,主函数main中通过调用add方法前加go来执行协程。

package main

import "fmt"

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

func main() {
   for i:=0; i<10; i++ {
      go add(i, i)
   }
}

如上实例所示,当运行main时,控制台并没有任何打印输出。原因为:main()函数启动了10个goroutine,然后返回,这时程序就退出了,而被启动的执行 Add() 的 goroutine 没来得及执行。我们想要让 main() 函数等待所有 goroutine 退出后再返回

2. sync.WaitGroup实现协程同步

sync.WaitGroup开箱即用,其是并发安全的。
WaitGroup拥有三个指针方法,可以想象该类型中有一个计数器,默认值是0,下面的方法就是操作或判断计数器:

  • Add : 增加或减少计数器的值。一般情况下,会用这个方法来记录需要等待的goroutine的数量
  • Done : 用于对其所属值中计数器的值进行减一操作,就是Add(-1),可以在defer语句中调用它
  • Wait : 阻塞当前的goroutine,直到所属值中的计数器归零。

​ 如下实例,将wg 计数设置为10, 每个for循环运行完毕都把计数器减一,主函数中使用Wait() 一直阻塞,直到wg为零(也就是所有的10个for循环都运行完毕)时,主程序main函数才会执行结束。

package main

import (
   "fmt"
   "sync"
)


func main() {

   wg := sync.WaitGroup{}
   wg.Add(10)
   for i:=0;i<10;i++ {
      //go Add1(i, i, wg)
      go func(i int, x int) {
         fmt.Println("执行add方法", i)
         z := i + i
         fmt.Println("执行add方法结束", i)
         fmt.Println(z)
         wg.Done()
      }(i, 1)

   }
   wg.Wait()

}

注意:定义参数wg可通过wg := sync.WaitGroup{}来定义,或者var wg sync.WaitGroup来定义。

3. Go的消息机制

​ 在Go中每个并发单元是自包含的、独立的个体,并且都有自己的变量,而且不同并发单元间这些变量不共享,如果不同并发单元之间要互相通信,就需要使用到信息机制(channel)。

​ channel 是 Go 语言在语言级别提供的 goroutine 间的通信方式,我们可以使用 channel 在多个 goroutine 之间传递消息。

channel的声明语法为:

`var` `chanName ``chan` `ElementType`

也可以使用内置函数 make() 定义一个channel:

ch(名称) := make(chan int(类型))

在channel的用法中,最常见的包括写入和读出:

// 将一个数据value写入至channel,这会导致阻塞,直到有其他goroutine从这个channel中读取数据
ch <- value

// 从channel中读取数据,如果channel之前没有写入数据,也会导致阻塞,直到channel中被写入数据为止
value := <-ch

默认情况下,channel的接收和发送都是阻塞的,除非另一端已准备好。

3.1 channel类型:无缓冲和缓冲类型

无缓冲类型:一个线程向这个channel发送了消息后,会阻塞当前的这个线程,知道其他线程去接收这个channel的消息。无缓冲的形式如下:

intChan := make(chan int)

带缓冲的channel类型,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理,带缓冲的形式如下:

//3为缓冲数量
intChan := make(chan int, 3)

如下实例定义结构体以及向channel中写入数据与获取数据,代码如下所示:

package main

import "fmt"

// 定义结构体
type Student struct {
   Name string
   Age  int
   Address Addr
}

type Addr struct {
   City string
}


func main() {

   // 定义channel
   stuchannel := make(chan Student, 8)

   // 向channel中写入数据
   stu := Student{"王海飞", 18, Addr{"成都"}}
   stuchannel <- stu

   // 修改stu中的信息
   stu.Age = 19

   // 取出channel中的数据
   stu_info := <- stuchannel
   fmt.Println("stu_info信息为:", stu_info)

}

注意: 通过channel传输自定义的Student对象,同时一端修改了数据,不影响另一端的数据,也就是说通过channel传递后的数据是独立的。

4. 生产者与消费者

​ 在Go中很容易实现生产者与消费者模式,以下实例定义一个生产者(其向channel通道中写入10个数据)与消费者(其从channel通道中取10个数据)。示例代码如下:

package main

import (
	"fmt"
	"time"
)

func product (p chan int) {
	for i:=0; i < 10; i++ {
		fmt.Println("send:", i)
		p <- i  // 向channel中写入数据

	}
}

func consumer (c chan int) {
	for i:=0; i<10; i++ {
		c := <- c  // 从channel中读取入数据
		fmt.Println("receive:", c)
	}
}

func main () {
	ch := make(chan int, 10)
	go product(ch)  // 创建一个生产者协程
	go consumer(ch)  // 创建一个消费者协程
	time.Sleep(time.Second)  // 等待足够长的时间,直到协程执行完毕主进程才结束
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夕Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值