Go编程入门详细例子-协程/管道

协程

package main
import (
	"fmt"
	"strconv"
	"time"
)

// 在主线程(可以理解成进程)中,开启一个goroutine, 该协程每隔1秒输出 "hello,world"
// 在主线程中也每隔一秒输出"hello,golang", 输出10次后,退出程序
// 要求主线程和goroutine同时执行

//编写一个函数,每隔1秒输出 "hello,world"
func test() {
	for i := 1; i <= 10; i++ {
		fmt.Println("tesst () hello,world " + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

func main() {

	go test() // 开启了一个协程

	for i := 1; i <= 10; i++ {
		fmt.Println(" main() hello,golang" + strconv.Itoa(i))
		time.Sleep(time.Second)
	}
}

协程-互斥锁

package main
import (
	"fmt"
	_ "time"
	"sync"
)

// 需求:现在要计算 1-200 的各个数的阶乘,并且把各个数的阶乘放入到map中。
// 最后显示出来。要求使用goroutine完成 

// 思路
// 1. 编写一个函数,来计算各个数的阶乘,并放入到 map中.
// 2. 我们启动的协程多个,统计的将结果放入到 map中
// 3. map 应该做出一个全局的.

var (
	myMap = make(map[int]int, 10)  
	//声明一个全局的互斥锁
	//lock 是一个全局的互斥锁, 
	//sync 是包: synchornized 同步
	//Mutex : 是互斥
	lock sync.Mutex
)

// test 函数就是计算 n!, 让将这个结果放入到 myMap
func test(n int) {
	
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}

	//这里我们将 res 放入到myMap
	//加锁
	lock.Lock()
	myMap[n] = res //concurrent map writes?
	//解锁
	lock.Unlock()
}

func main() {

	// 我们这里开启多个协程完成这个任务[200个]
	for i := 1; i <= 20; i++ {
		go test(i)
	}


	//休眠10秒钟【第二个问题 】
	//time.Sleep(time.Second * 5)

	//这里我们输出结果,变量这个结果
	lock.Lock()
	for i, v := range myMap {
		fmt.Printf("map[%d]=%d\n", i, v)
	}
	lock.Unlock()

}

管道

package main
import (
	"fmt"
)

func main() {

	//演示一下管道的使用
	//1. 创建一个可以存放3个int类型的管道
	var intChan chan int
	intChan = make(chan int, 3)

	//2. 看看intChan是什么
	fmt.Printf("intChan 的值=%v intChan本身的地址=%p\n", intChan, &intChan)


	//3. 向管道写入数据
	intChan<- 10
	num := 211
	intChan<- num
	intChan<- 50
	// //如果从channel取出数据后,可以继续放入
	<-intChan
	intChan<- 98//注意点, 当我们给管写入数据时,不能超过其容量


	//4. 看看管道的长度和cap(容量)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan)) // 3, 3

	//5. 从管道中读取数据

	var num2 int
	num2 = <-intChan 
	fmt.Println("num2=", num2)
	fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan))  // 2, 3

	//6. 在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock

	num3 := <-intChan
	num4 := <-intChan

	//num5 := <-intChan

	fmt.Println("num3=", num3, "num4=", num4/*, "num5=", num5*/)

}

管道-存放任意类型

package main
import (
	"fmt"
)

type Cat struct {
	Name string
	Age int
}

func main() {

	//定义一个存放任意数据类型的管道 3个数据
	//var allChan chan interface{}
	allChan := make(chan interface{}, 3)

	allChan<- 10
	allChan<- "tom jack"
	cat := Cat{"小花猫", 4}
	allChan<- cat

	//我们希望获得到管道中的第三个元素,则先将前2个推出
	<-allChan
	<-allChan

	newCat := <-allChan //从管道中取出的Cat是什么?

	fmt.Printf("newCat=%T , newCat=%v\n", newCat, newCat)
	//下面的写法是错误的!编译不通过
	//fmt.Printf("newCat.Name=%v", newCat.Name)
	//使用类型断言
	a := newCat.(Cat) 
	fmt.Printf("newCat.Name=%v", a.Name)
	

}

管道-关闭

package main
import (
	"fmt"
)

func main() {

	intChan := make(chan int, 3)
	intChan<- 100
	intChan<- 200
	close(intChan) // close
	//这是不能够再写入数到channel
	//intChan<- 300
	fmt.Println("okook~")
	//当管道关闭后,读取数据是可以的
	n1 := <-intChan
	fmt.Println("n1=", n1)


	//遍历管道
	intChan2 := make(chan int, 100)
	for i := 0; i < 100; i++ {
		intChan2<- i * 2  //放入100个数据到管道
	}

	//遍历管道不能使用普通的 for 循环
	// for i := 0; i < len(intChan2); i++ {

	// }
	//在遍历时,如果channel没有关闭,则会出现deadlock的错误
	//在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
	close(intChan2)
	for v := range intChan2 {
		fmt.Println("v=", v)
	}


}

协程与管道综合案例

package main
import (
	"fmt"
	"time"
)


//write Data
func writeData(intChan chan int) {
	for i := 1; i <= 50; i++ {
		//放入数据
		intChan<- i //
		fmt.Println("writeData ", i)
		//time.Sleep(time.Second)
	}
	close(intChan) //关闭
}

//read data
func readData(intChan chan int, exitChan chan bool) {

	for {
		v, ok := <-intChan
		if !ok {
			break
		}
		time.Sleep(time.Second)
		fmt.Printf("readData 读到数据=%v\n", v) 
	}
	//readData 读取完数据后,即任务完成
	exitChan<- true
	close(exitChan)

}

func main() {

	//创建两个管道
	intChan := make(chan int, 10)
	exitChan := make(chan bool, 1)
	
	go writeData(intChan)
	go readData(intChan, exitChan)

	//time.Sleep(time.Second * 10)
	for {
		_, ok := <-exitChan
		if !ok {
			break
		}
	}

}

编译器运行,如果发现一个管道只有写没有读,则管道会阻塞。但是读管道的频率比写管道慢,是可以的,写管道会等待读管道读取后写入。

协程/管道最佳实践

需求:
要求统计 1-200000 的数字中,哪些是素数?这个问题在本章开篇就提出了,现在我们有 goroutine
和 channel 的知识后,就可以完成了 [测试数据: 80000]

package main
import (
	"fmt"
	"time"
)



//向 intChan放入 1-8000个数
func putNum(intChan chan int) {

	for i := 1; i <= 80000; i++ {    
		intChan<- i
	}

	//关闭intChan
	close(intChan)
}

// 从 intChan取出数据,并判断是否为素数,如果是,就
// 	//放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {

	//使用for 循环
	// var num int
	var flag bool // 
	for {
		//time.Sleep(time.Millisecond * 10)
		num, ok := <-intChan //intChan 取不到..
		
		if !ok { 
			break
		}
		flag = true //假设是素数
		//判断num是不是素数
		for i := 2; i < num; i++ {
			if num % i == 0 {//说明该num不是素数
				flag = false
				break
			}
		}

		if flag {
			//将这个数就放入到primeChan
			primeChan<- num
		}
	}

	fmt.Println("有一个primeNum 协程因为取不到数据,退出")
	//这里我们还不能关闭 primeChan
	//向 exitChan 写入true
	exitChan<- true	

}

func main() {

	intChan := make(chan int , 1000)
	primeChan := make(chan int, 20000)//放入结果
	//标识退出的管道
	exitChan := make(chan bool, 8) // 4个



	start := time.Now().Unix()
	
	//开启一个协程,向 intChan放入 1-8000个数
	go putNum(intChan)
	//开启4个协程,从 intChan取出数据,并判断是否为素数,如果是,就
	//放入到primeChan
	for i := 0; i < 8; i++ {
		go primeNum(intChan, primeChan, exitChan)
	}

	//这里我们主线程,进行处理
	//直接
	go func(){
		for i := 0; i < 8; i++ {
			<-exitChan
		}

		end := time.Now().Unix()
		fmt.Println("使用协程耗时=", end - start)

		//当我们从exitChan 取出了4个结果,就可以放心的关闭 prprimeChan
		close(primeChan)
	}()


	//遍历我们的 primeChan ,把结果取出
	for {
		_, ok := <-primeChan
		if !ok{
			break
		}
		//将结果输出
		//fmt.Printf("素数=%d\n", res)
	}

	fmt.Println("main线程退出")
}

猜你喜欢

转载自blog.csdn.net/uisoul/article/details/105921408