Go语言之gorountine与管道初体验

本文介绍了Go语言中的协程(goroutine)特性,包括其轻量级线程的特点、如何创建与调度,以及并发编程的最佳实践。通过示例展示了使用管道(channel)进行线程安全的数据传递,并探讨了类型断言、异常处理和管道的关闭与遍历。此外,还提到了如何避免资源竞争问题和协程的异常处理策略。
摘要由CSDN通过智能技术生成

Go之gorountine

一个go线程上,可以启动多个协程。协程是轻量级的线程。
特点:

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

go的协程机制,可以轻松的开启上万个协程。其他编程语言的并发机制基于线程,开启过多的线程,它们的资源耗费大。

package main

import (
	"fmt"
	"strconv"
	"time"
)

func main(){
	go test() // main主线程和test协程同时执行
	for i := 0; i < 5; i++ { // 主线程执行5次,协程执行6次就跟着退出
		fmt.Println("main() hello main..."+strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

func test(){
	for i := 0; i < 10; i++ {
		fmt.Println("test() hello test..."+strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

显示和设置cpu

package main

import (
	"fmt"
	"runtime"
)

func main() {
	cpuNum := runtime.NumCPU()

	runtime.GOMAXPROCS(cpuNum) // 默认可不设置
	fmt.Println("cpu个数为:",cpuNum)
}

多个协程并发求阶乘之最佳解法-channel(队列)

  1. 法1:低水平,全局变量互斥锁解决。如下
package main

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

var(
	myMap = make(map[int]int,10)
	lock sync.Mutex // 全局变量互斥锁
)
func test(n int){
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	// 将res放入map中
	lock.Lock()
	myMap[n] = res
	lock.Unlock()
}

func main() {
	//cpuNum := runtime.NumCPU()
	//
	//runtime.GOMAXPROCS(cpuNum) // 默认可不设置
	//fmt.Println("cpu个数为:",cpuNum)

	// 开启多个协程
	for i := 0; i < 20; i++ {
		go test(i)
	}

	time.Sleep(time.Second*5)

	lock.Lock()
	// 遍历输出的结果
	for i,v := range myMap{
		fmt.Printf("map[%v]=%v\n",i,v)
	}
	lock.Unlock()

}
  1. 主线程要专门等待几秒,这样显然不可以。
    主线程再等待所有协程全部完成的时间 无法确定,以上代码设置5秒,这显然不好,仅仅只是估算。

新的通讯机制,管道channel - 队列

channel管道(队列)线程安全的

多个协程同时操作同一个管道时,不会发生资源竞争问题
channel是引用类型,必须初始化后才可写入数据,即make后才可使用
管道是有类型的,intChan只能写入整数int
管道不可自动增长,而map可以自动扩容。

管道的初始化,读、取数据

存满后,不可再存数据了。可边存边取,如流动的数据,流动的水。

package main

import "fmt"

func main(){
	// 管道的初始化
	var channel chan int
	channel = make(chan int,3)
	
	// 向管道存数据,之后即可读数据
	channel <- 5 // first
	num1 := 666
	channel <- num1
	fmt.Printf("管道的地址%v\n",channel)

	// 从管道取数据,FIFO
	fmt.Println("前",len(channel),cap(channel))
	var num2 int
	num2 = <- channel
	fmt.Println("后",len(channel),cap(channel))
	fmt.Println(num2) // FIFO 5先进
}

类型断言的最佳实践

package main

import "fmt"

func main(){
	var channel chan interface{}
	channel = make(chan interface{},10)

	cat1:=Cat{"小橘",3}
	cat2:=Cat{"灰白",2}
	channel <- cat1
	channel <- cat2
	channel <- 10
	channel <- "lwt"

	cat11 := <- channel

	fmt.Printf("cat11的类型%T,值%v\n",cat11,cat11)
	// 但是这里却不能cat11.Name,??? 因为编译器认为这是空接口

	// 类型断言的最佳实践
	newCat := cat11.(Cat)
	fmt.Println(newCat.Name)
}

type Cat struct {
	Name string
	Age int
}

管道的关闭和遍历(先关闭再遍历)

在遍历管道前,先关闭管道,防止发生死锁。

关闭

关闭后可读不可取。(类似加写锁)
close(管道名 )

遍历

必须使用for-range(前提先关闭管道),且无索引返回。
若不关闭将报错如下:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()

package main

import "fmt"

func main(){

	channel := make(chan int,2)
	channel <- 10
	channel <- 99

	//close(channel)
	// 管道关闭后,只可取,不可存
	num1 := <- channel
	//channel <- 999
	fmt.Println(num1)

	// 当管道关闭后,才可遍历,for-range无索引
	for v := range channel{
		fmt.Println(v)
	}
}

只写管道
var chan1 chan<- int
chan1 = make(chan int,3)
只读管道
var chan2 <-chan int
chan2 = make(chan int,3)

使用场景:
通常默认声明,才封装的方法参数中传入引用类型管道,ch chan<- int或ch <-chan int

协程的异常处理

如果我们启动的协程出现异常错误panic,必须捕获这个panic,否则造成整个程序崩溃。这是我们使用recover()返回的err来输出预警。这样主线程和其他协程不受到影响

package main

import (
	"fmt"
	"time"
)

func main(){
	go sayHello()
	go makeError()
	for i := 0; i < 5; i++ {
		time.Sleep(time.Second)
		fmt.Println("main()防止主线程跑完")
	}
}

func sayHello(){
	for i := 0; i < 5; i++ {
		time.Sleep(time.Second)
		fmt.Println(i)
	}
}

func makeError(){

	defer func() {
		// 捕获test抛出的panic
		if err:=recover();err!=nil {
			fmt.Println("makeError()发生错误,发生预警,err=",err)
		}
	}()
	var map1 map[int]string
	//map1 = make(map[int]string,3)
	map1[0] = "111"

}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值