• 196. go goroutine && channel


    1. 入门

    func print_hello() {
    	// go 协成模型可以认真阅读, 常常见识
    	// https://i6448038.github.io/2017/12/04/golang-concurrency-principle/#:~:text=Go%E7%BA%BF%E7%A8%8B%E5%AE%9E%E7%8E%B0%E6%A8%A1%E5%9E%8BMPG%20M%20%E6%8C%87%E7%9A%84%E6%98%AF%20Machine%20%EF%BC%8C%E4%B8%80%E4%B8%AA%20M%20%E7%9B%B4%E6%8E%A5%E5%85%B3%E8%81%94%E4%BA%86%E4%B8%80%E4%B8%AA%E5%86%85%E6%A0%B8%E7%BA%BF%E7%A8%8B%E3%80%82,P%20%E6%8C%87%E7%9A%84%E6%98%AF%E2%80%9Dprocessor%E2%80%9D%EF%BC%8C%E4%BB%A3%E8%A1%A8%E4%BA%86%20M%20%E6%89%80%E9%9C%80%E7%9A%84%E4%B8%8A%E4%B8%8B%E6%96%87%E7%8E%AF%E5%A2%83%EF%BC%8C%E4%B9%9F%E6%98%AF%E5%A4%84%E7%90%86%E7%94%A8%E6%88%B7%E7%BA%A7%E4%BB%A3%E7%A0%81%E9%80%BB%E8%BE%91%E7%9A%84%E5%A4%84%E7%90%86%E5%99%A8%E3%80%82%20G%20%E6%8C%87%E7%9A%84%E6%98%AF%20Goroutine%20%EF%BC%8C%E5%85%B6%E5%AE%9E%E6%9C%AC%E8%B4%A8%E4%B8%8A%E4%B9%9F%E6%98%AF%E4%B8%80%E7%A7%8D%E8%BD%BB%E9%87%8F%E7%BA%A7%E7%9A%84%E7%BA%BF%E7%A8%8B%E3%80%82
    	for i := 0; i < 10; i++ {
    		fmt.Println("hello, world, " + strconv.Itoa(i))
    		time.Sleep(time.Second)
    	}
    }
    
    func main() {
    	go print_hello() // go goroutine  # 使用go语法启动一个goroutine 协成
    }
    print_hello()
    

    2. 获取cpu数

    
    func get_cpu_num() {
    	num := runtime.NumCPU()
    	runtime.GOMAXPROCS(num)
    	fmt.Println("num=", num)
    	// 1.8之前需要手动设置多核, 之后的就不需要了
    }
    func main() {
    	get_cpu_num() // 获取cpu数
    }
    
    

    3. go数据安全问题(使用锁解决线程安全问题)

    func test(n int) {
    	// 使用锁解决线程安全问题
    	res := 1
    	for i := 1; i <= n; i++ {
    		res += i
    	}
    	lock.Lock() // 如果不适用lock会发生即读又写, 出现线程安全问题
    	m[n] = res
    	lock.Unlock()
    }
    
    func main() {
    	var m = make(map[int]int)
    	m[1] = 1
    	for i := 1; i < 200; i++ {
    		go test(i) // 使用goroutine计算, 1-200个数的阶乘, 每个数的阶乘级绿道map中
    	}
    	time.Sleep(time.Second * 10)
    	fmt.Print(m)
    }
    

    4. go 管道

    func test2() {
    	var intChan chan int
    	intChan = make(chan int, 10)
    
    	intChan <- 10
    	intChan <- 12
    	intChan <- 13
    	// intChan <- 10  // fatal error: all goroutines are asleep - deadlock! 超过容量报错
    
    	// num1 := <-intChan
    	// num2 := <-intChan
    	// num3 := <-intChan
    	// num4 := <-intChan // fatal error: all goroutines are asleep - deadlock! 空channel取值报错
    	// fmt.Print(num1, num2, num3)
    
    	close(intChan) // 如果遍历未关闭的管道, 遍历完所有元素后, 后报错(关闭的管道可以读, 不可以写)
    	// for v := range intChan {
    	// 	fmt.Println(v)
    	// }
    	// for {
    	// 	v, ok := <-intChan
    	// 	if !ok {
    	// 		fmt.Println(v, ok) // 当数据读完, 并且管道关闭了,v会变成管道类型的默认值, ok=false
    	// 		break
    	// 	}
    	// 	fmt.Println(v, ok)
    	// }
    
    	for i := 0; i < 4; i++ {
    		<-intChan
    	}
    
    }
    
    func main() {
    	test2() // 管道
    }
    
    

    5. 管道练习,读写数据

    func writeData(c chan int) {
    	for i := 1; i <= 50; i++ {
    		c <- i
    		fmt.Println("writeData, data=", i)
    		// time.Sleep(time.Second * 3)
    	}
    	close(c)
    }
    func readData(c chan int, e chan bool) {
    	for {
    		v, ok := <-c // 管道没数据就会阻塞
    		if !ok {
    			break
    		}
    		time.Sleep(time.Second * 3)
    		fmt.Printf("readData 读到数据=%v
    ", v)
    	}
    	e <- true
    	close(e)
    }
    
    func test3() {
    	/*
    		问题:如果注销掉go readData(int(han,exitChan),程序会怎么样?
    		答:如果只是向管道写入数据,而没有读取,就会出现阻塞而dead lock,原因是intChan容量是10,
    		而代码writeData会写入50个数据,因此会阻塞在writeData的 ch <- i
    		解释上面: 也就是说如果程序发现一个管道只有写没有度, 通过管道容量比较小(比如容量10, 写入500个元素, 就会触发dead lock)
    
    		但是你看上面代码, 我的管道容量是1, 我写入了50个数据, 而且writeData我将sleep注释掉了, 也没有报错
    		说明编译器发现这个管道在被消费, 这个时候写阻塞, 等待管道数据被消费
    	*/
    	intChan := make(chan int, 10)
    	exitchan := make(chan bool, 1)
    	go writeData(intChan)
    	go readData(intChan, exitchan)
    	for {
    		_, ok := <-exitchan
    		if !ok {
    			break
    		}
    	}
    
    }
    
    func main() {
    	test3() // 管道练习,读写数据
    }
    
    

    6. 使用goroutine计算, 1-20000个数中的素数

    var isPirme chan int = make(chan int, 4)
    var exit chan bool = make(chan bool, 1)
    
    func check(a int) bool {
    	for i := 2; i < int(math.Sqrt(float64(a))); i++ {
    		if a%i == 0 {
    			return false
    		}
    	}
    	return true
    }
    
    func IsPrime(ch chan int, exit chan bool) {
    	for {
    		n1, ok := <-ch
    		if !ok {
    			break
    		}
    		ok = check(n1)
    		if ok {
    			fmt.Printf("%v 是素数
    ", n1)
    		}
    	}
    	exit <- true
    }
    
    func test4() {
    	// 使用goroutine计算20000以内数字的素数
    	go func() {
    		for i := 0; i <= 200; i++ {
    			isPirme <- i
    		}
    		close(isPirme)
    	}()
    
    	for i := 0; i < 4; i++ {
    		go IsPrime(isPirme, exit)
    	}
    
    	// 这种方式不好
    	// count := 0
    	// for {
    	// 	count++
    	// 	if count > 4 {
    	// 		close(exit)
    	// 	}
    	// 	_, ok := <-exit
    	// 	if !ok {
    	// 		break
    	// 	}
    
    	// }
    	// 可以通过for遍历, 效果类似上面, 但是for循环自己判断管道是否关闭, 不需要你自己处理
    	for i := 0; i < 4; i++ {
    		<-exit
    	}
    	close(exit)
    }
    
    func main() {
    	test4() // 使用goroutine计算, 1-20000个数中的素数
    }
    
    

    7. 只读只写管道

    func test5() {
    	var ch2 chan<- int // 只写管道
    	ch2 = make(chan<- int, 3)
    	ch2 <- 20
    	// num := <- ch2 // error
    
    	fmt.Println("ch2=", ch2)
    
    	var ch3 <-chan int // 只读管道
    	num2 := <-ch3      // 默认会阻塞, 但是go程序不可能一直阻塞, go会检测如果没回写入或者获取时, 会报错
    	// ch3 <- 100 // error
    	fmt.Println("num2=", num2)
    }
    func main(){
      test5()
    }
    

    8.使用 select 可以解决从管道取数据的阻塞问题

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func test6() {
    	ch2 := make(chan int, 10)
    	for i := 0; i < 10; i++ {
    		ch2 <- i
    	}
    
    	sch := make(chan string, 5)
    	for i := 0; i < 5; i++ {
    		sch <- "hello" + fmt.Sprintf("%d", i)
    	}
    	/*
    		//传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock
    		//问题,在实际开发中,可能我们不好确定什么关闭该管道.
    		//可以使用 select 方式可以解决
    	*/
    
    	//label:
    	for {
    		select {
    		/*
    			//注意: 这里,如果 intChan 一直没有关闭,不会一直阻塞而 deadlock
    			//,会自动到下一个 case 匹配
    		*/
    		case v := <-ch2:
    			fmt.Printf("从 intChan 读取的数据%d
    ", v)
    			time.Sleep(time.Second)
    		case v := <-sch:
    			fmt.Printf("从 intChan 读取的数据%s
    ", v)
    			time.Sleep(time.Second)
    		default:
    			fmt.Printf("都取不到了,不玩了, 程序员可以加入逻辑
    ")
    			time.Sleep(time.Second)
    			return
    			// break label
    		}
    	}
    }
    
    func main() {
    
    	test6() // 使用 select 可以解决从管道取数据的阻塞问题
    }
    

    9.go goroutine中异常处理

    不要让一个线程崩溃导致主线程崩溃

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func sayHello() {
    	for i := 0; i <= 10; i++ {
    		time.Sleep(time.Second)
    		fmt.Println("hello world")
    	}
    }
    
    func test8() {
    
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println("test() 发生错误, ", err)
    		}
    	}()
    
    	var m1 map[int]string
    	m1[0] = "golang" //error
    }
    func test7() {
    	/*
    		4) goroutine 中使用recover,解决协程中出现 panic,导致程序崩溃问题
    		说明:如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生的问题,
    		但是主线程仍然不受影响,可以继续执行。
    	*/
    	go sayHello()
    	go test8()
    	for i := 0; i < 10; i++ {
    		fmt.Println("main() ok=", i)
    		time.Sleep(time.Second)
    	}
    
    }
    
    func main() {
    
    	test7() // 使用recover,解决协程中出现 panic,导致程序崩溃问题
    
    }
    
  • 相关阅读:
    迅为龙芯2K1000开发板虚拟机ubuntu安装软件
    迅为恩智浦IMX6Q开发板系统固件TF卡烧写
    瑞芯微迅为iTOP-3399开发板资料更新啦!最新版本为1.3版本
    迅为-龙芯2K1000开发板虚拟机ubuntu系统开关机
    迅为IMX6ULL开发板Linux驱动初探-最简单的设备驱动-helloworld
    迅为-龙芯2K1000开发板虚拟机ubuntu基础操作
    开始Blog
    16 bit 的灰度图如何显示
    GPU 总结
    同步,异步,多线程和事件总结
  • 原文地址:https://www.cnblogs.com/liuzhanghao/p/15357170.html
Copyright © 2020-2023  润新知