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,导致程序崩溃问题

}
原文地址:https://www.cnblogs.com/liuzhanghao/p/15357170.html