Go并发学习(一)——goroutine和channel

线程和协程

线程:操作系统能够进行运算调度的最小单位。一个进程可以包含多个线程,是进程中的实际运作单位。

**协程:**又称微线程。协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

为什么有了线程还需要协程

虽然多线程在前互联网世代已经足够使用,但是线程的局限性也比较明显

1.线程数量有限,一般不会很多
2.线程占据的资源通常比我们需要的多得多,造成浪费

每个系统级线程开辟都会占用空间,这个空间可能是MB级别,但是我们如果使用的线程只需要传递KB级别数据,那么线程看起来就会比较浪费,但是又不可避免。而且线程之间的切换也会占用一些额外开销。

为了解决上述的问题,引入了协程(goroutine):比线程更轻量,更少的资源消耗,动态调配资源

协程的优点
1.协程之间的切换由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
2.不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

在golang中,goroutine的创建消耗非常小,大约是KB级别。因此可以创建更多的协程,尤其是数量越多相对线程优势更加明显,而且goroutine可以动态伸缩,栈溢出风险也比线程更低。

golang的并发与goroutine的使用

var name = "yiyiyinhe"

func changeName() {
    name = "change"
}

func sayHi() {
    fmt.println("hi, ", name)
    go changeName() // 协程
}


只需要使用go关键字,我们就创建了一个简单的协程,但是demo输出的可能是
1.“hi,yiyiyinhe”
2.“hi,change”
这是因为协程的执行顺序是不可预估的

如果是想对一个代码块使用协程,如下所示

var name = "yiyiyinhe"

func sayHi() {
    fmt.println("hi, ", name)
    go func() { // 匿名函数执行协程
        name = "change"
    }
}


channel

什么是channel

channel是一种通信机制,它让一个goroutine向另一个goroutine发送值。每个通道都是特定类型(通道元素类型)值的管道。

我们通过信道操作符<-来发送或者接收数据

ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。

如何创建channel

创建的channel分为有缓存和无缓存两种,区别就是创建的时候是否分配大小

1.无缓存channel

var ch1 = make(chan int)//未分配大小
var ch2 = make(chan int, 0)//分配大小为0也等同给于未分配大小

2.有缓存channel

var :ch3 = make(int, 3)//分配大小为3的有缓存channel

默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。

在这里插入图片描述
还是用上述的例子

var name = "yiyi"
var flag = make(chan bool) // 创建了bool类型的channel

func changeName() {
    name = "change"
    flag <- true  // 发送
}

func sayHi() {
    go changeName() // 协程
    <-flag // 接收
    fmt.println("hi, ", name)
}

这时候的输出结果就一定是"hi,change"

对照着上图,我们知道尾部的接手必须发生在头部的发生操作之前,所以在<-flag这一步的时候头部的发送操作一定是完成的了,也就表明此时的name已经是"change",实现了一个简单的**同步操作 **

main()也可以看做一个协程

在没有使用channel的时候,我们来看一个例子:

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Reveal romantic feelings...")
	go sendLove()
	go responseLove()
	waitFor()
	fmt.Println("Leaving ☠️....")
}

func waitFor() {
	for i := 0; i < 5; i++ {
		fmt.Println("Keep waiting...")
		time.Sleep(1 * 1e9)
	}
}

func sendLove() {
	fmt.Println("Love you, mm ❤️")
}

func responseLove() {
	time.Sleep(6 * 1e9)
	fmt.Println("Love you, too")
}

不难看出输出结果如下

Reveal romantic feelings…
Love you, mm ❤️
Keep waiting…
Keep waiting…
Keep waiting…
Keep waiting…
Keep waiting…
Leaving ☠️…

为什么我们明明已经将回答写进程序中,但是并没有输出呢——这是因为在回应输出之前,main方法已经执行完了,这直接就导致所有的协程都停止工作,自然也就没办法输出了

但是借助channel,我们就可以实现main协程和子协程之间的通信了

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)
	var answer string

	fmt.Println("Reveal fomantic feelings...")
	go sendLove()
	go responseLove(ch)
	waitFor()
	answer = <-ch //1.这一步在没有数据发送之前会被阻塞

	if answer != "" {
		fmt.Println(answer)
	} else {
		fmt.Println("Dead ☠️....")
	}

}

func waitFor() {
	for i := 0; i < 5; i++ {
		fmt.Println("Keep waiting...")
		time.Sleep(1 * 1e9)
	}
}

func sendLove() {
	fmt.Println("Love you, mm ❤️")
}

func responseLove(ch chan string) {
	time.Sleep(6 * 1e9)
	ch <- "Love you, too"// 2.第一步必须等待这行代码执行完成才可以继续运行
}

我们借助channel,就实现了一个简易的协程同步控制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值