文章目录
前言
一些大问题可以分解成小任务。goroutine可以让程序同时处理几个不同的任务。goroutine可以使用channel来协调它们的工作,channel允许goroutine互相发送数据并同步,这样一个goroutine就不会领先于另一个goroutine。goroutine让你充分利用具有多处理器的计算机,让程序运行得更快
1. 检索网页
在开始之前我们先来看这么一个例子,我们需要检索一个网页上面的所有的内容保存起来。
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func responseSize(url string){
response, err := http.Get(url) //使用我们要检索的URL来调用http.Get
if err != nil{
log.Fatal(err)
}
defer response.Body.Close() //延迟执行, 一旦main函数退出,就释放网络连接
body, err := ioutil.ReadAll(response.Body) //读取响应中的所有数据
if err != nil{
log.Fatal(err)
}
fmt.Println(string(body)) //将数据转换为字符串并打印
}
func main() {
responseSize("https://www.baidu.com/")
}
现在我们可以使用这个函数单独检索多个页面了,但是带来了一个问题,如果页面太多,那么我们一个一个检索就很慢了,所以我们得想一个方法来并发执行。
2. 使用goroutine的并发性
在Go中,并发任务叫做goroutine,其他编程有一个类似的概念叫做线程,但是goroutine比线程需要更少的计算机内存,启动和停止的时间更短,这意味着你可以同时运行更多的goroutine。
调用方法:go 函数调用
,比如
go myFunction()
go otherFunction("argument")
注意这里我们说的是另一个goroutine,因为每个go程序的main函数都是使用goroutine启动的,因此每个Go程序至少需要运行一个goroutine
3. 使用goroutine
看下面这个程序,我们启动多个goroutine来分别for循环50次打印,记得主线程调用sleep函数,因为这三个方法都是在不同goroutine里面执行的,所以主线程必须等待其他两个线程执行打印结果。和Java的是一样的,java可以使用 join
package main
import (
"fmt"
"time"
)
func a () {
for i := 0; i < 50; i++ {
fmt.Println("a")
}
}
func b () {
for i := 0; i < 50; i++ {
fmt.Println("b")
}
}
func main() {
go a()
go b()
time.Sleep(time.Second)
}
至于输出结果,就和并发一样了,谁先执行谁后执行都是不确定的。
4. 在responseSize函数中使用goroutine
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
func responseSize(url string){
response, err := http.Get(url) //使用我们要检索的URL来调用http.Get
if err != nil{
log.Fatal(err)
}
defer response.Body.Close() //延迟执行, 一旦main函数退出,就释放网络连接
body, err := ioutil.ReadAll(response.Body) //读取响应中的所有数据
if err != nil{
log.Fatal(err)
}
fmt.Println(string(body)) //将数据转换为字符串并打印
}
func main() {
go responseSize("https://www.baidu.com/")
go responseSize("https://www.baidu.com/")
go responseSize("https://www.baidu.com/")
time.Sleep(5 * time.Second)
}
5. go语句不能有返回值
切换到goroutine带来了另一个需要解决的问题:我们不能在go语句中使用函数返回值。假设我们想要返回网页的大小,而不是打印它:
我们可以看到编译错误的提醒,编译器阻止你尝试从使用go语句调用的函数中获取返回值。其实go也是考虑了函数不能及时返回的情况,总不能让main函数一直等吧。
6. 使用channel发送和接收值
我们虽然不能有返回值,但是可以使用channel进行goroutine之间的通信,channel不仅允许你将值从一个goroutine发送到另一个goroutine,还确保在接收的goroutine尝试使用该值之前,发送的goroutine已经发送该值。
- 创建一个channel
- 编写一个函数,该函数接收一个channel作为参数,我们将在一个单独的goroutine中运行这个函数,并使用它通过channel发送值
- 在初始的goroutine中接收发送的值
每个channel只携带特定类型的值,因此可能有一个channel用于int值,另一个channel用于struct类型的值。要声明包含channel的变量,可以使用chan关键字,然后是channel将携带的值的类型。
var myChannel chan float64
创建channel:
var myChannel chan float64 //声明一个变量来保存channel
myChannel = make(chan float64) //实际创建channel
使用短变量声明:
myChannel := make(chan floaat64)
要在channel上发送值,可以使用 <- 运算符(这是一个小于号后面跟着一个英文破折号)。
(发送到的channel)myChannel <- 3.14(通过channel发送的值)
也可以使用 <- 来接受
<- myChannel(从中接收)
,myChannel就是你要从哪个channel接收值
使用channel:我们通过get方法重写从使用channel,任何再获取channel的值
package main
func greeting(myChannel chan string){
myChannel <- "hi" //通过channel发送一个值
}
func main() {
myChannel := make(chan string)
go greeting(myChannel)
println(<- myChannel) //hi
}
当然了,我们也可以把channel中的值接收到一个变量中存着
package main
import "fmt"
func greeting(myChannel chan string) {
myChannel <- "hi" //通过channel发送一个值
}
func main() {
myChannel := make(chan string)
go greeting(myChannel)
returnValue := <-myChannel
fmt.Printf("returnValue: %v\n", returnValue)
}
7. 同步goroutine和channel
实际上,channel确保了发送的goroutine在接收channel尝试使用该值之前已经发送了该值。channel通过bocking(阻塞) – 暂停当前goroutine中的所有进一步操作来实现这一点。发送操作阻塞发送的goroutine,直到这个goroutine的值被取出来了。
package main
import "fmt"
func abc(channel chan string) {
channel <- "a"
channel <- "b"
channel <- "c"
}
func def(channel chan string) {
channel <- "d"
channel <- "e"
channel <- "f"
}
func main() {
channel1 := make(chan string)
channel2 := make(chan string)
go abc(channel1)
go def(channel2)
fmt.Println(<-channel1) //a
fmt.Println(<-channel2) //d
fmt.Println(<-channel1) //b
fmt.Println(<-channel2) //e
fmt.Println(<-channel1) //c
fmt.Println(<-channel2) //f
}
可以看到其实顺序是可以确定的,因为abc函数中顺序是a,b,c,那么结果其实就是函数abc中顺序是a、b、c,而def中的顺序是d、e、f,因为给channel赋值之后就要阻塞等到获取之后才继续存下一个值
8. 使用channel修复我们的网页大小程序
首先我们不需要返回值了,其次我们把channel作为参数传到方法中
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func responseSize(url string, channel chan int) {
response, err := http.Get(url) //使用我们要检索的URL来调用http.Get
if err != nil{
log.Fatal(err)
}
defer response.Body.Close() //延迟执行, 一旦main函数退出,就释放网络连接
body, err := ioutil.ReadAll(response.Body) //读取响应中的所有数据
if err != nil{
log.Fatal(err)
}
channel <- len(body)
}
func main() {
sizes := make(chan int)
go responseSize("https://www.baidu.com/", sizes)
go responseSize("https://www.baidu.com/", sizes)
go responseSize("https://www.baidu.com/", sizes)
fmt.Println(<-sizes)
fmt.Println(<-sizes)
fmt.Println(<-sizes)
//227
//227
//227
}
最后我们其实可以使用for循环来发送的,这样代码更加简洁,同时我们也可以设置channel是结构体类型,都是一样的
如有错误,欢迎指出!!!