go语言goroutine(协程)和channel(管道)学习

go语言管道和协程小记

package main

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

var (
	myMap = make(map[int]uint64, 10)
	lock  sync.Mutex
)

func test() {

	for i := 0; i < 7; i++ {
		fmt.Println("hello,world" + strconv.Itoa(i))
		time.Sleep(time.Second)

	}
}

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

	// }

	for i := 1; i <= 200; i++ {
		go cal(i)
	}
	time.Sleep(time.Second * 5)
	lock.Lock()
	for i, v := range myMap {
		fmt.Printf("map[%d]%d\n", i, v)
	}
	lock.Unlock()
}
func cal(n int) {
	var res uint64 = 1
	for i := 1; i <= n; i++ {
		res *= uint64(i)
	}
	lock.Lock()
	myMap[n] = res
	lock.Unlock()
}
  • 上面的代码为低级的用法,使用到了锁,后面用管道来进行解决。
  • 下面是管道的一些用法,要注意接口类型的管道
package main

import (
	"fmt"
)

func main() {
	var intChan chan int
	var mapChan chan map[string]int
	// var sliceChan chan []map[int]int

	intChan = make(chan int, 3)
	fmt.Printf("intchan  %T  %p\n", intChan, &intChan)

	intChan <- 10
	num := 211
	intChan <- num
	fmt.Printf("intchan  len = %v cap = %v ", len(intChan), cap(intChan))

	num2 := <-intChan
	intChan <- <-intChan
	fmt.Printf("intchan  len = %v cap = %v \n", len(intChan), cap(intChan))
	fmt.Println(num2)

	mapChan = make(chan map[string]int, 30)
	m1 := make(map[string]int, 10)
	m1["北京"] = 10
	m1["tianjin"] = 10
	m2 := make(map[string]int, 10)
	m2["北京"] = 10
	m2["tianjin"] = 10
	mapChan <- m1
	mapChan <- m2
	fmt.Printf("intchan  len = %v cap = %v \n", len(mapChan), cap(mapChan))
	//下面是interface{}类型的管道注意
	var interChan chan interface{}

	interChan = make(chan interface{}, 10)
	s1 := make([]int, 10)
	s1 = []int{10, 20}
	type Cat struct {
		Name string
		Age  int
	}
	type Dog struct {
		Name string
	}

	interChan <- "hello"
	interChan <- 111
	interChan <- 111.111
	interChan <- s1
	cat := Cat{
		Name: "qiuqiu",
		Age:  2,
	}
	interChan <- cat
	str := <-interChan
	<-interChan
	<-interChan
	slice := <-interChan
	newCat := <-interChan
	fmt.Printf("%T\n%v\n", str, str)
	fmt.Printf("%T\n%v\n", slice, slice)
	fmt.Printf("%T\n%v\n", newCat, newCat)
	_, ok := newCat.(Dog)
	//类型断言,故意举个错误的例子,联系一下类型断言是返回两个值,一个值原来的值,一个是布尔值
	/*正常是
	a,ok := newCat(Cat)
	if ok {
		fmt.Println(a.Name)
	}
	*/
	if !ok {
		fmt.Println("a.Name")
	}

	fmt.Printf("interchan  len = %v cap = %v \n", len(interChan), cap(interChan))

	//channel 关闭时不可以写,但是可以读
	//遍历管道,用 for-range
	intChan2 := make(chan int, 100)
	for i := 1; i <= 100; i++ {
		intChan2 <- i * 2
	}
	//遍历时,如果channel没有关闭,则会出现deadlock的错误:fatal error: all goroutines are asleep - deadlock!
	//如果已经关闭管道,则会正常遍历数据,遍历完后,退出遍历
	close(intChan2)
	//管道只返回一个值,没有所谓的下标
	for v := range intChan2 {
		fmt.Printf("值为%d\n", v)
	}
	/*
		上面这个情况是明确知道管道什么时候关闭,用for range来遍历,
		当不知道什么时候关闭,仍想遍历时,用select方法
		label:
		for{
			select{
				//注意:这里,如果intChan一直没有关闭,不会一直阻塞而deadlock
				//会自动匹配到下一个case
				case v:=<-intChan:
					fmt.Printf("从intChan取出值为%d\n", v)
				case v:=<-stringChan:
					fmt.Printf("从stringChan取出值为%s\n", v)
				default:
					fmt.Println("都取不到值了,程序员可以加入自己的逻辑")
					//return //用return直接退出整个程序或者用label,不推荐
					break label

			}
		}
	*/
}

  • 管道解决问题时,注意要再开一个管道用来存储状态,如下
package main

import (
	"fmt"
	"time"
)

/*
解决一个实际的问题,
同时向一个管道进行写入数据和输出数据
一个管道正常存入存储数据
再开一个退出管道用来记录上面这个管道的状态,如果读完数据,在这个管道写入true,并关闭退出管道

然后在主线程一直for循环读取退出管道,读到true退出
*/

func WriteData(intchan chan int) {
	for i := 1; i <= 100; i++ {
		intchan <- i * 2
		fmt.Println("写入数据", i*2)

	}
	close(intchan)
}
func ReadData(intchan chan int, exitchan chan bool) {
	for {
		v, ok := <-intchan
		if !ok {
			break
		}
		fmt.Println(v)
		time.Sleep(time.Second)
	}
	exitchan <- true
	close(exitchan)
}

func main() {

	intchan := make(chan int, 10)
	exitchan := make(chan bool, 1)
	go WriteData(intchan)
	go ReadData(intchan, exitchan)
	for {
		_, ok := <-exitchan
		if !ok {
			break

		}
	}

}

  • 管道和协程解决一个问题,求素数,用到了上面的想法
package main

import (
	"fmt"
	"time"
)

/*
求素数
*/
//放数据
func putNum(intChan chan int) {
	//这里要是想变成只写的,可以这样传参intChan chan<- int,这样可以将本来是双向的在这个函数里面变成只写,避免误操作
	//只读也一样,变成 intChan <-chan int

	for i := 1; i < 8000; i++ {
		intChan <- i
	}
	close(intChan)
}

// 取数据
func primeNum(intChan, primeChan chan int, exitChan chan bool) {
	// var num int
	var flag bool
	for {
		time.Sleep(time.Millisecond)
		num, ok := <-intChan
		if !ok {
			break
		}
		flag = true
		for i := 2; i < num; i++ {
			if num%i == 0 {
				flag = false
				break
			}
		}
		if flag {
			primeChan <- num
		}
	}
	fmt.Println("有一个协程退出")
	//这里不能关闭primeChan,可能其他还在处理,向一个退出管道写入true即可
	exitChan <- true
}
func main() {
	intChan := make(chan int, 8000)
	primeChan := make(chan int, 3000)
	exitChan := make(chan bool, 4)
	//放数
	go putNum(intChan)
	//判断是否为素数
	for i := 1; i <= cap(exitChan); i++ {
		go primeNum(intChan, primeChan, exitChan)
	}
	//当写入四个true时,才关闭管道
	go func() {
		for {
			if len(exitChan) == 4 {
				close(primeChan)
				break
			}
		}
	}()
	//遍历primeChan,取出数字
	for {
		res, ok := <-primeChan
		if !ok {
			break
		}
		fmt.Println(res)
	}
	fmt.Println("main exit")

}

  • 对于不想因为一个协程出错而导致其他的不能运行,可以在协程适当位置添加recover来捕获panic
package main

import (
	"fmt"
	"time"
)

func sayHello() {
	for i := 1; i < 10; i++ {
		fmt.Printf("sayHello,%d\n", i)
	}
}
func test() {
	var myMap map[string]int
	//故意没有make一下,引出错误处理机制,之前学过defer+recover
	defer func() {
		//捕获panic
		if err := recover(); err != nil {
			fmt.Println("发生错误,", err)
		}
	}()
	myMap["1"] = 1
}
func main() {
	go sayHello()
	go test()
	for i := 1; i < 10; i++ {
		fmt.Printf("main , %d\n", i)
		time.Sleep(time.Second)
	}
}

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值