Go语言之协程和管道

一、协程

1.Go主线程也可称为线程,也可以理解为进程,一个Go主线程上可以起多个协程,可以理解协程为轻量级的线程,资源消耗较小
2.协程特点:有独立的栈空间;共享程度堆空间;调度由用户控制;是轻量级的线程

package main

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

/**
main主线程和test协程同时执行
*/
func main() {
	go test() //开启协程
	for i := 0; i < 10; i++ {
		fmt.Println("hello wrold ", strconv.Itoa(i+1), "次")
		time.Sleep(time.Second)
	}
}

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

二、管道

1.为什么需要channel

主线程等待所有goroutine全部完成时间很难确定,等待时间短协程会随主线程的退出而销毁,等待时间长,不利程序运行。另外全局变量加锁同步实现通讯,也不利于多协程对全局变量的读写操作。协程间通信,共享数据。

2.channel的介绍

  • 本质是一个数据结构----队列
  • 线程安全,多协程访问时,不需要加锁,设计上不会发生数据竞争
  • 是有类型的,一个string的channel只能存放string类型数据
  • 是引用类型,必须初始化make后才能写入数据
  • 无缓冲通道,阻塞住,不取走无法放,实现同步通信,缓冲通道实现异步通信
  • 如果写入管道数据大于容量并且没有读该管道会阻塞,如果有读即使读写频率不一致,也不会阻塞
package main

import (
	"fmt"
)

func main() {
	//声明一个可读可写管道,初始化管道必须make
	var intChan chan int
	intChan = make(chan int, 3)
	//声明为只能写
	var chan1 chan<- int
	chan1 = make(chan int, 3)
	chan1 <- 11
	//声明为只能读
	//var chan2 <-chan int

	//向管道写入数据,写入数据不能超过长度
	intChan <- 10
	num := 211
	intChan <- num
	intChan <- 985

	//向管道读取数据,如果已经没有数据在读取,会报deadlock错误
	n := <-intChan
	fmt.Println("取出的数据", n)

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

	//关闭channel,关闭后只能读,不能写入
	close(intChan)

	//channel遍历,不能用普通的for循环
	//必须关闭channel,否则报错
	for i := range intChan {
		fmt.Println(i)
	}

	intChan1 := make(chan int, 10)
	strChan := make(chan string, 10)
	for i := 0; i < 10; i++ {
		intChan1 <- i
		strChan <- "hello" + fmt.Sprintf("%d", i)
	}
	selectChannel(intChan1, strChan)
}

//不使用close()也可以从管道中取出数据
//实际开发中,不好确定什么时候关闭管道,可以用select方式解决
func selectChannel(intChan1 chan int, strChan chan string) {
	for {
		select {
		//如果intChan一直没有关闭,不会一直堵塞至死锁,自动跳到下一个case
		case v := <-intChan1:
			fmt.Printf("从intChan1中取数据%d\n", v)
		case v := <-strChan:
			fmt.Printf("从strChan中取数据%s\n", v)
		default:
			fmt.Println("都取不到了")
			return
		}
	}
}

三、协程与管道综合应用

package main

import "fmt"

func writeData(intChan chan int) {

	for i := 0; i < 50; i++ {
		intChan <- i
		fmt.Println("写入的数据", i)
	}
	//没关闭前读线程已经开始工作
	//如果没有关闭,编译器会一直等待下一个写入的内容,导致死锁
	close(intChan)

}
func readData(intChan chan int, exitChan chan bool) {
	//如果此协程发生错误,捕获异常,不影响其他协程,主线程不受影响
	defer func() {
		//捕获readData抛出的panic
		err := recover()
		if err != nil {
			fmt.Println("发生错误")
		}
	}()
	for {
		v, ok := <-intChan
		if !ok {
			break
		}
		fmt.Println("读取到的数据", v)
	}
	exitChan <- true
	close(exitChan)
}
func main() {
	intChan := make(chan int, 50)
	//借助此管道判断两个协程什么时候结束
	exitChan := make(chan bool, 1)
	go writeData(intChan)
	go readData(intChan, exitChan)
	//解决主线程等待协程时间问题,实现同步
	for {
		_, ok := <-exitChan    //如果协程没有完成,会一直阻塞在这里
		if !ok {
			break
		}
	}
}

四、同步实现

package main

import (
	"fmt"
	"runtime"
	"sync"
	"time"
)

//WaitGroup 实现同步
//等待组,启动协程加入,执行结束移除
//当等待组没有内容的时候,通知另外一个线程可以执行了
var wp sync.WaitGroup

func main() {
	for i := 0; i < 10; i++ {
		go showInt(i)
		wp.Add(1) //启动一个协程就+1
	}
	wp.Wait() //等待所有登记的协程结束
	fmt.Println("协程都结束,主线程执行...")

	//runtime.Gosched() 的使用
	//执行结果java java Golang Golang
	go showString("java")
	for i := 0; i < 2; i++ {
		runtime.Gosched() //我有权利执行任务,但让给协程来执行
		fmt.Println("Golang")
	}

	//runtime.Goexit(),结束协程
	go show()
	time.Sleep(time.Second) //如果不休眠,show()不能执行
}

func show() {
	for i := 0; i < 10; i++ {
		if i >= 5 {
			runtime.Goexit()
		}
		fmt.Printf("i:%v\n", i)
	}
}

func showString(str string) {
	for i := 0; i < 2; i++ {
		fmt.Println(str)
	}
}

func showInt(i int) {
	//deger wp.Add(-1)
	defer wp.Done() //协程结束就-1
	fmt.Println(i)
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值