Loading

Golang goroutine 和 channel使用

golang基础教程

一、Go 协程和 Go 主线程

Go 主线程(有程序员直接称为线程/也可以理解成进程): 一个 Go 线程上,可以起多个协程,你可以
这样理解,协程是轻量级的线程[编译器做优化]
Go 协程的特点

  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 协程是轻量级的线程

二、goroutine使用

1、一个简单的案例

  1. 在主线程(可以理解成进程)中,开启一个 goroutine, 该协程每隔 1 秒输出 “hello,world”
  2. 在主线程中也每隔一秒输出"hello,golang", 输出 10 次后,退出程序
    同时进行
func test(){
	for i:=0;i<10;i++{
		time.Sleep(time.Second)
		fmt.Println("hello world")
	}
}
func main() {
	go test()
	for i:=0;i<10;i++{
		time.Sleep(time.Second)
		fmt.Println("hello golang")
	}
}

2、总结:

  1. 主线程是一个物理线程,直接作用在 cpu 上的。是重量级的,非常耗费 cpu 资源。
  2. 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
  3. Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一
    般基于线程的,开启过多的线程,资源耗费大,这里就突显 Golang 在并发上的优势了

三、channel使用

1、为什么用channel

异步累加案例:

var s =0

func sum(a int){
s+=a
}
func main() {
for i:=0;i<100;i++{
go sum(i)
}
time.Sleep(time.Second*2)
fmt.Println(s)
}

每次执行出现的结果可能会不一样
问题一、什么时候累加结束?
问题二、协程A累加后的s值会覆盖协程B累加后的s值吗?
如何解决?

  1. 全局变量的互斥锁
  2. 使用管道 channel 来解决

2、全局变量的互斥锁

对s进行加锁,并加入n告诉程序什么时候累加结束。

var (
	s = 0
	n = 0
	lock sync.Mutex
)

func sum(a int){
lock.Lock()
defer lock.Unlock()
s+=a
n++

}
func main() {
for i:=0;i<100;i++{
go sum(i)
}
for{
if n == 100{
break
}
}
fmt.Println(s)
}

似乎这个已经完美解决了所有问题。但是我们使用线程的目的是什么?效率

  1. 加锁了就是要等依次执行,协程A执行完成加锁的代码才能让出资源供协程B执行
  2. 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。

这里就需要使用channel了

3、channel 的基本介绍

  1. channle 本质就是一个数据结构-队列【示意图】
  2. 数据是先进先出【FIFO : first in first out】
  3. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  4. channel 有类型的,一个 string 的 channel 只能存放 string 类型数据。
  5. 示意图:
    在这里插入图片描述

4、定义/声明 channel

var 变量名 chan 数据类型

举例:

var intChan chan int (intChan 用于存放 int 数据)
var mapChan chan map[int]string (mapChan 用于存放 map[int]string 类型)
var perChan chan Person
var perChan2 chan *Person

channel 是引用类型
channel 必须初始化才能写入数据, 即 make 后才能使用
管道是有类型的,intChan 只能写入 整数 int

func main() {
//演示一下管道的使用
//1. 创建一个可以存放 3 个 int 类型的管道
var intChan chan int
intChan = make(chan int, 3)
//2. 看看 intChan 是什么
fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan)
//3. 向管道写入数据
intChan<- 10
num := 211
intChan<- num
intChan<- 50
// intChan<- 98//注意点, 当我们给管写入数据时,不能超过其容量
//4. 看看管道的长度和 cap(容量)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3
//5. 从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 2, 3
//6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock
num3 := <-intChan
num4 := <-intChan
num5 := <-intChan
fmt.Println("num3=", num3, "num4=", num4, "num5=", num5)
}

5、channel 使用的注意事项

  1. channel 中只能存放指定的数据类型
  2. channle 的数据放满后,就不能再放入了
  3. 如果从 channel 取出数据后,可以继续放入
  4. 在没有使用协程的情况下,如果 channel 数据取完了,再取,就会报 dead lock
  5. goroutine 中使用 recover,解决协程中出现 panic,导致程序崩溃问题

在这里插入图片描述

goroutine一个累加小案例

posted @ 2023-07-05 15:06  猫鱼故巷  阅读(15)  评论(0编辑  收藏  举报