zoukankan      html  css  js  c++  java
  • 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,导致程序崩溃问题
    
    }
    
  • 相关阅读:
    【LeetCode】048. Rotate Image
    【LeetCode】036. Valid Sudoku
    【LeetCode】060. Permutation Sequence
    【LeetCode】001. Two Sum
    【LeetCode】128. Longest Consecutive Sequence
    【LeetCode】081. Search in Rotated Sorted Array II
    【LeetCode】033. Search in Rotated Sorted Array
    顺时针打印矩阵
    矩形覆盖
    二维数组中的查找
  • 原文地址:https://www.cnblogs.com/liuzhanghao/p/15357170.html
Copyright © 2011-2022 走看看