go 协程与管道

本文深入探讨Go语言中的并发特性,包括协程的创建与注意事项,互斥锁与读写锁的使用确保数据同步,管道的定义、遍历及读写操作,以及如何利用defer+recover机制处理协程中的错误。通过示例代码详细阐述了Go语言在并发编程中的实践应用。
摘要由CSDN通过智能技术生成


协程

  • 用户态的轻量级线程;单个线程,用户态控制上下文切换;
  • 开启一个协程:go func_name()

注意事项

  • 如果主线程退出了,则协程即使还没有执行完毕,也会退出;
  • 协程在完成了自己的认为后,先于主线程结束;
  • 为了防止主线程提前退出,使用:time.Sleep(tmie.Second*5); sync.WaitGroup.Wait(); sync.WaitGroup 可反复使用;

互斥锁同步协程

  • 多个协程操作同一个数据;需要加入互斥锁;Mutex
  • sync 包类型的值,不应被拷贝;高水平的同步使用管道更好一些;
  • func (m *Mutex) Lock(); func (m *Mutex) Unlock();
  • 使用 Lock() 加锁后,不能再次加锁,直到调用 UnLock() 解锁后,才能再次加锁;适用于读写次数没有明显区别的场景;性能,效率相对较低;

读写锁

  • 读写锁:RWMutex;
  • 适用于读次数远远多于写次数的场景;
  • 读数据的时候,数据之间不会相互影响,写和读之间才会产生影响;

管道

  • 管道本质就是一个数据结构-队列;
  • 数据是先进先出;
  • 自身保证线程安全,多个协程访问时,不需要加锁;
  • 管道是有类型的,一个 string 的管道只能存放 string 类型的数据;

管道的定义

  • var 变量名 chan 数据类型:var ch chan string;
  • chan 是管道的关键字;
  • 操作函数,buildin包下:长度:len(intChan);容量:cap(intChan);关闭管道:close(intChan)
  • 数据类型是管道里的数据的类型;也即是管道的类型;
  • 管道是引用类型,必须初始化才能写入数据,即 make 后才能使用;intChan = make(chan int, 3),容量为3,不能存放大于容量的数据;
  • 管道的关闭: 管道关闭后,就不能再向管道写数据了,但是仍然可以从管道读取数据;
  • 关于 close 函数:
func close(c chan<- Type)
内建函数close关闭信道,该通道必须为双向的或只发送的。它应当只由发送者执行,而不应由接收者执行,其效果是在最后发送的值被接收后停止该通道。
在最后的值从已关闭的信道中被接收后,任何对其的接收操作都会无阻塞的成功。

对于已关闭的信道,语句:x, ok := <-c
还会将ok置为false

管道的遍历

  • 使用for-range 遍历;
  • 在遍历时,如果管道没有关闭,则会出现 deadlock 的错误;
  • 在遍历时,如果管道 已经关闭,则会正常遍历数据,遍历完成后,就会退出遍历;
  • 管道数据没有准备好时,会阻塞等待;

只读/写管道

  • 管道可以声明未只读或者只写性质;
  • 只写:var intC chan <- int; 只读:var intC <-chan int; 注意箭头位置;

管道的 select 功能

  • 解决多个管道的选择问题,也可以叫作多路复用,可以从多个管道中随机公平的选择一个来执行;
  • case 后面必须进行的是 io 操作,不能是等值,随机去选择一个io操作;
  • default: 防止 select 被阻塞主,加入 default;

defer + recover 机制处理错误

  • 多个协程工作,其中一个协程出现panic,导致程序崩溃;
  • 解决:利用 defer + recover 捕获 panic 进行处理,即使协程出现问题,主线程仍然不受影响可以继续执行;
func divide () {
	defer func(){
		err := recover()
		if err != nil {
			fmt.Println(" divide() 函数异常:", err)
		}
	}()

	num1 := 10
	num2 := 0
	fmt.Println(num1, "/" ,num2 ,"=", num1/num2)
}

示例代码

package main

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

// 只定义无需赋值
var wg sync.WaitGroup
var sum int
var lock sync.Mutex

func add() {
	defer wg.Done()
	for i := 0; i < 10; i++ {
		sum = i + 1
	}
}

func test() {
	for i := 0; i < 5; i++ {
		wg.Add(1) // 协程开始的时候加1
		go func(n int) {
			fmt.Println(n)
			wg.Done() // 协程执行完成减一
		}(i)
	}
	// 阻塞,直到wg 减为零
	wg.Wait()
}

func mutexAdd(){
	defer wg.Done()
	for i := 0; i < 10_0000; i++ {
		// 加锁
		lock.Lock()
		sum += 1
		lock.Unlock()
	}
}

func mutexSub(){
	defer wg.Done()
	for i := 0; i < 10_0000; i++ {
		// 加锁
		lock.Lock()
		sum -= 1
		lock.Unlock()
	}
}

var rwLock sync.RWMutex
func RWLockRead() {
	defer wg.Done()
	rwLock.RLock()
	fmt.Println("读取数据...")
	time.Sleep(time.Second)
	fmt.Println("读取数据成功...")
	rwLock.RUnlock()
}
func RWLockWrite() {
	defer wg.Done()
	rwLock.Lock()
	fmt.Println("==>>修改数据...")
	time.Sleep(time.Second * 10)
	fmt.Println("==>>修改数据完成...")
	rwLock.Unlock()
}

func chanTest() {
	// 定义一个int 类型的管道
	var intChan chan int
	// 通过make 初始化,管道可以存储3个int 类型的数据
	intChan = make(chan int, 3)
	fmt.Printf("查看/证明管道的引用类型,intChan=%v\n", intChan)

	fmt.Println("==>> 向管道写入数据:")
	intChan <- 10
	num := 20
	intChan <- num
	intChan <- 30
	// intChan <- 18 // 大于管道容量,报错:fatal error: all goroutines are asleep - deadlock! \n goroutine 1 [chan send]:
	fmt.Println("intChan 管道的长度:", len(intChan))
	fmt.Println("intChan 管道的容量:", cap(intChan))

	fmt.Println("==>> 从管道读取数据:")
	num1 := <- intChan
	fmt.Println("读到的第一个数据:", num1)
	fmt.Println("intChan 管道的长度:", len(intChan))
	fmt.Println("intChan 管道的容量:", cap(intChan))

	fmt.Println("==>> 未关闭管道,遍历:")
	// 报错:fatal error: all goroutines are asleep - deadlock!
	// forRangeChan(intChan)

	fmt.Println("==>> 关闭管道:")
	close(intChan)
	// 关闭后写入报错: panic: send on closed channel
	// intChan <- 40
	num2 := <- intChan
	fmt.Println("管道关闭后,可以读取数据,第二个数据:", num2)

	fmt.Println("==>> 关闭管道,遍历:")
	forRangeChan(intChan)

}

func forRangeChan(c chan int) {
	for val := range c {
		fmt.Println("val=", val)
	}
}

func writeData(intChan chan int)  {
	defer wg.Done()
	for i := 0; i < 20; i++ {
		intChan <- i
		fmt.Println("向管道中写入数据:", i)
		time.Sleep(time.Second)
	}
	close(intChan)
}
func readData(intChan chan int)  {
	defer wg.Done()
	forRangeChan(intChan)
}

func goroutineXChan(){
	wg.Add(2)
	intChan := make(chan int, 50)
	go writeData(intChan)
	go readData(intChan)
	wg.Wait()
}

func readOnlyWriteOnlyChan(){
	var intChanWrite chan <- int // 只写
	intChanWrite = make(chan int, 3)
	intChanWrite <- 10
	// num := <- intChanWrite // 报错:
	fmt.Println("intChanWrite=", intChanWrite)

	var intChanRead <- chan int // 只读
	if intChanRead != nil {
		num :=  <- intChanRead
		fmt.Println("num=", num)
	}
}

func chanSelect() {
	wg.Add(2)
	ints := make(chan int, 1)
	go func() {
		time.Sleep(time.Second * 5)
		ints <- 10
		wg.Done()
	}()


	strChan := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		strChan <- "go-go"
		wg.Done()
	}()

	select {
		case v := <- ints:
			fmt.Println("ints=", v)
		case v := <- strChan:
			fmt.Println("strChan=", v)
		default:
			fmt.Println("防止 select 被阻塞")
	}
	wg.Wait()
}

func printNum() {
	for i := 0; i < 10; i++ {
		fmt.Println("i=", i)
	}
}

// divide 做除法: 除0 异常
func divide () {
	defer func(){
		err := recover()
		if err != nil {
			fmt.Println(" divide() 函数异常:", err)
		}
	}()

	num1 := 10
	num2 := 0
	fmt.Println(num1, "/" ,num2 ,"=", num1/num2)
}

func deferRecoverTest() {
	go divide()
	go printNum()
	time.Sleep(time.Second * 5)
}

func main() {
	fmt.Println("==>> test:")
	test()

	fmt.Println("==>> add:")
	wg.Add(1)
	go add()
	wg.Wait()
	fmt.Println("sum=", sum)

	fmt.Println("==>> Mutex 使用示例:")
	wg.Add(2)
	go mutexAdd()
	go mutexSub()
	wg.Wait()
	fmt.Println("sum=", sum)

	fmt.Println("\n==>> RWMutex 使用示例:")
	wg.Add(6)

	// 多个协程读,读多写少的场景
	for i := 0; i < 5; i++ {
		go RWLockRead()
	}
	go RWLockWrite()
	wg.Wait()
	fmt.Println("sum=",sum)

	fmt.Println("\n==>> 管道:")
	chanTest()

	fmt.Println("\n==>> 协程和管道:")
	goroutineXChan()

	fmt.Println("\n==>> 只读/写管道:")
	readOnlyWriteOnlyChan()

	fmt.Println("==>> select-chan:")
	chanSelect()

	fmt.Println("==>> defer+recover:")
	deferRecoverTest()
}

运行结果

==>> test:
4
0
1
2
3
==>> add:
sum= 10
==>> Mutex 使用示例:
sum= 10

==>> RWMutex 使用示例:
==>>修改数据...
==>>修改数据完成...
读取数据...
读取数据...
读取数据...
读取数据...
读取数据...
读取数据成功...
读取数据成功...
读取数据成功...
读取数据成功...
读取数据成功...
sum= 10

==>> 管道:
查看/证明管道的引用类型,intChan=0xc00020e000
==>> 向管道写入数据:
intChan 管道的长度: 3
intChan 管道的容量: 3
==>> 从管道读取数据:
读到的第一个数据: 10
intChan 管道的长度: 2
intChan 管道的容量: 3
==>> 未关闭管道,遍历:
==>> 关闭管道:
管道关闭后,可以读取数据,第二个数据: 20
==>> 关闭管道,遍历:
val= 30

==>> 协程和管道:
向管道中写入数据: 0
val= 0
向管道中写入数据: 1
val= 1
向管道中写入数据: 2
val= 2
向管道中写入数据: 3
val= 3
向管道中写入数据: 4
val= 4
向管道中写入数据: 5
val= 5
向管道中写入数据: 6
val= 6
向管道中写入数据: 7
val= 7
向管道中写入数据: 8
val= 8
向管道中写入数据: 9
val= 9
向管道中写入数据: 10
val= 10
向管道中写入数据: 11
val= 11
向管道中写入数据: 12
val= 12
向管道中写入数据: 13
val= 13
向管道中写入数据: 14
val= 14
向管道中写入数据: 15
val= 15
向管道中写入数据: 16
val= 16
向管道中写入数据: 17
val= 17
向管道中写入数据: 18
val= 18
向管道中写入数据: 19
val= 19

==>> 只读/写管道:
intChanWrite= 0xc00020e080
==>> select-chan:
防止 select 被阻塞
==>> defer+recover:
i= 0
i= 1
i= 2
i= 3
i= 4
i= 5
i= 6
i= 7
i= 8
i= 9
 divide() 函数异常: runtime error: integer divide by zero

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值