- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
提及Go,大家都会人云亦云一句“Go支持高并发,适合使用高并发的场景”,事实也确实如此,Go学习笔记系列也终于到了该介绍下最著名的Go并发的时候了,没有介绍并发的Go文章是没有灵魂的哈哈^^
提及并发,很容易联想到另外一个概念:并行。它们两个的区别是:
一个并发程序可以只在一个处理器或内核上运行多个任务,但是某一时间点只有一个任务在运行;如果运行在多处理器或多核上,就可以在同一时间点有多个任务在运行,才能实现真正的并行,因此,并发程序可以是运行在单处理器(单核)上也可以是运行在多处理器(多核)上。而在Go中,可以设置核数,让并发程序在多核心上真正并行运行,充分发挥多核计算机的能力
在其它编程语言中,实现并发程序往往是使用多线程的技术。在一个进程中有多个线程,它们共享同一个内存地址空间。然而使用多线程难以做到准确,尤其是内存中数据共享的问题,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果。多线程解决这个问题的方式是同步不同的线程,对数据加锁
但是,这会带来更高的复杂度,更容易使代码出错以及更低的性能,所以这个经典的方法明显不再适合现代多核/多处理器编程
在Go中,使用协程(goroutines)来实现并发程序:
sync
包可以进行传统的加锁同步操作,但并不推荐使用在Go中使用协程是通过关键字go调用一个函数或者方法来实现的:
import (
"fmt"
"time"
)
func main(){
go Goroutine()
time.Sleep(3 * time.Second)
}
func Goroutine(){
fmt.Println("start a goroutine")
}
根据上面的代码,我们定义了一个Goroutine
的函数,并在主程序中使用go
关键字去调用该函数,从而启动一个协程去执行Goroutine
这个函数。在程序中使用time.Sleep(3*time.Second)
是为了让主程序延时3s再结束,否则主程序启动完一个协程后立即退出,我们将没法看到协程函数中打印的信息
新版本的Go(应该是1.8之后)当我们启动多个协程时,Go将会自动启动多个核心来并行运行,而在老版本的Go里需要我们手动设置,手动设置多核心的操作如下:
import (
"fmt"
"runtime"
"time"
)
func main(){
num := runtime.NumCPU()
runtime.GOMAXPROCS(num) //新版本会自动设置
for i := 0; i < 10; i++ {
go Goroutine(i)
}
time.Sleep(3 * time.Second)
}
func Goroutine(){
fmt.Println("start a goroutine")
}
通道(channel)是一种特殊的类型,可以理解为发送某种其它类型数据的管道,用于在协程之间通信。数据利用通道进行传递,在任何时间,通道中的数据只能被一个协程进行访问,因此不会发生数据竞争
make
进行创建,使用close
来关闭var ch1 chan string
ch1 = make(chan string) //创建一个用于传递string类型的通道
如果只是声明channel,未初始化,它的值为nil
上面两行代码也可以简写为ch1 := make(chan string)
通道的操作符<-
往通道发送数据:ch <- int1
表示把变量int1
发送到通道ch
中
从通道接收数据:int2 <- ch
表示变量int2
从通道ch
中接收数据(如果int2
没有事先声明过,则要用int2 := <- ch
)。直接使用<-ch
也可以,也表示从通道中取值,然后该值会被丢弃
func main(){
c := make(chan bool)
go func() { //使用匿名函数,闭包,所以可以获取到外层的channel变量
fmt.Println("go go go")
c <- true
}()
<-c //阻塞,直到从通道取出数据
}
上面的代码创建了一个布尔型的channel,主程序启动协程后就一直阻塞在<-c
那里等待从通道中取出数据,协程中当打印完数据后,就往通道中发送true
。主程序此时方从通道中取出数据,退出程序。从而不需要手动让主程序睡眠等待协程完成
func main(){
c := make(chan bool)
go func() { //使用匿名函数,闭包,所以可以获取到外层的channel变量
fmt.Println("go go go")
<-c
}()
c <- true //这里把数据传入通道后也会阻塞知道通道中数据被取出
}
func main(){
c := make(chan bool, 1)
go func() { //使用匿名函数,闭包,所以可以获取到外层的channel变量
fmt.Println("go go go")
<-c
}()
c <- true //这里往通道发完数据就直接退出了
}
make(chan type, buf)
这里buf
是通道可以同时容纳的元素的个数,如果容量大于 0,通道就是异步的了:缓冲满载(发送)或变空(接收)之前通信不会阻塞,元素会按照发送的顺序被接收
for-range
来操作channelfor-range
可以用在通道上,以便从通道中获取值:for v := range ch {
fmt.Println(v)
}
它从指定的通道中读取数据直到通道关闭才能执行下面的代码,因此程序必须在某个地方close
该通道,否则程序将死锁
func main(){
c:=make(chan bool)
go func(){
fmt.Println("gogogo")
c <- true
close(c)
}()
for v := range c{
fmt.Println(v)
}
}
此外,关于channel还需要注意:
使用select
可以从不同的并发执行的协程中获取值,它和switch
语句很类似。select
可以用来监听进入通道的数据,也可以向通道发送数据
select {
case u:= <- ch1:
...
case v:= <- ch2:
...
...
default: // no value ready to be received
...
}
select
的功能其实就是处理列出的多个通信中的一个
default
语句是可选的,fallthrough
是不允许的,任何一个case
中执行了break
或者return
语句,select
就结束了case
的通道都阻塞了,会等待直到其中一个可以处理case
的通道可以处理,会随机
选择一个处理default
语句,它就会执行default
语句select
中使用发送操作并且有default
可以确保发送不被阻塞!如果没有default
,select
就会一直阻塞select
也可以设置超时处理下面的代码是一个类似生产者-消费者的模式,包括了两个通道和三个协程,其中协程goroutine1
和goroutine2
分别往通道ch1
和ch2
中写入数据,协程goroutine3
则通过select
分别从两个通道中读出数据并输出
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go goroutine1(ch1)
go goroutine2(ch2)
go goroutine3(ch1, ch2)
time.Sleep(1e9)
}
func goroutine1(ch chan int) {
for i := 0; ; i++ {
ch <- i * 2
}
}
func goroutine2(ch chan int) {
for i := 0; ; i++ {
ch <- i + 5
}
}
func goroutine3(ch1, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("Received on channel 1: %d\n", v)
case v := <-ch2:
fmt.Printf("Received on channel 2: %d\n", v)
}
}
}
我正在使用 go 图表库 https://github.com/wcharczuk/go-chart制作条形图。我面临的问题是标签值很长,我想将文本旋转 45 度以显示完整文本 我喜欢显示的日期格式是
我在构建一个非常简单的通过 cgo 调用 c 代码的 go 程序时遇到了问题。我的设置: $: echo $GOPATH /go $: pwd /go/src/main $: ls ctest.c
没有 C 的背景,只有 Go 的“初学者”经验,我正在尝试弄清楚 main.go 是实际需要的还是只是一个约定。 我想创建一个简单的网络 API,但有人可以为我澄清一下吗? 最佳答案 main.go
我read从 Go 1.4 开始,Go 运行时是用 Go 本身编写的(而不是用 C)。 这怎么可能?如果 Go 程序在运行时之上运行,并且运行时是 Go 程序,那么运行时是否在自身之上运行? 最佳答案
这是“Go 之旅”中的代码示例 Range and Close : package main import ( "fmt" ) func fibonacci(n int, c chan int
给定以下 go.mod 文件: module foo go 1.12 require ( github.com/bar/baz v1.0.0 github.com/rat/cat v1
我有一个 CI/CD 管道,它需要跨平台并与几个不同的管理程序一起工作。为了不必更改 Windows 和 Linux 的构建任务,我认为 Go 将是编写一次代码并在任何地方运行的好方法。然而,考虑到
我有一个 Dockerfile,用于使用 go build 编译 Go 应用程序。我进行了研究,确实建议将 go build 用于生产。 但是我找不到正确的答案来解释为什么。 我了解 go run 创
我尝试在命令提示符#Go lang 中运行该程序-但是当我键入运行“go run hello.go”命令时,我开始了 CreateFile hello.go:The system cannot fin
我正在使用“Go 编程语言”一书学习 Go。第一章介绍os.Open用于读取文件的模块。我尝试打开如下所示的 go 文件。 f, err = os.Open("helloworld.go") 我收
关闭。这个问题需要details or clarity .它目前不接受答案。 想改进这个问题?通过 editing this post 添加详细信息并澄清问题. 2年前关闭。 Improve this
为了解决我对 goroutine 的一些误解,我去了 Go 操场跑了 this code : package main import ( "fmt" ) func other(done cha
这个问题在这里已经有了答案: Evaluate/Execute Golang code/expressions like js' eval() (5 个回答) 1年前关闭。 对于任何 go 程序,我想
这是我基本上试图从路径打印基准的代码。 这意味着,如果用户输入“/some/random/path.java”,则输出将为“path”。同样,如果用户arg为“/another/myapp.c”,则输
$ go version 1.13.3 我的文件夹结构如下: GOPATH +---src +--- my-api-server +--- my-auth-server
这个问题在这里已经有了答案: How to embed file for later parsing execution use (4 个答案) What's the best way to bun
我觉得这有点奇怪,为什么这段代码不起作用? package main import "fmt" func main() { var i, j int = 1, 2 k
go编译器执行完如下命令后的可执行文件存放在哪里? $> go run file.go 最佳答案 在 /tmp 文件夹中,如果您使用的是 unix 机器。 如果您使用的是 Windows,则在 \Us
我目前正在开始使用 Go,并且已经深入研究了有关包命名和工作区文件夹结构的注意事项。 不过,我不太确定如何根据 Go 范式正确组织我的代码。 这是我当前的结构示例,它位于 $GOPATH/src 中:
假设我有一个接受用户输入的 Lua 程序,而该输入恰好是有效的 Lua 源代码。这是在程序仍在运行时进行清理、编译和执行的。 Go 是否(或将)实现这样的事情? 最佳答案 我认为以下两个项目之间有足够
我是一名优秀的程序员,十分优秀!