Skip to content

Commit

Permalink
go 并发
Browse files Browse the repository at this point in the history
  • Loading branch information
webtao520 committed Nov 2, 2020
1 parent ee3944c commit 8478fc6
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 1 deletion.
40 changes: 40 additions & 0 deletions Go语言并发/Go语言轻量级线程/1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"time"
)

func running(){
var times int
// 构建一个无限循环
for {
times ++
fmt.Println("tick",times)
// 延迟1s
time.Sleep(time.Second)
}
}


func main(){
// 并发执行程序
go running()
// 接受命令行输入 不做任何事情
var input string
fmt.Scanln(&input)
}


/**
PS D:\goLang\github\golang_project\Go语言并发\Go语言轻量级线程> go run 1.go
tick 1
tick 2
tick 3
tick 4
tick 5
tick 6
tick 7
tick 8
tick 9
*/
27 changes: 27 additions & 0 deletions Go语言并发/Go语言轻量级线程/2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package main

import(
"fmt"
"time"
)

func main (){
// 匿名函数创建goroutine
go func(){
var times int
for {
times ++
fmt.Println("tick",times)
time.Sleep(time.Second)
}
}()
var input string
fmt.Scanln(&input)
}

/**
PS D:\goLang\github\golang_project\Go语言并发\Go语言轻量级线程> go run 2.go
tick 1
tick 2
tick 3
*/
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,38 @@ go 函数名( 参数列表 )
2) 例子
使用 go 关键字,将 running() 函数并发执行,每隔一秒打印一次计数器,而 main 的 goroutine 则等待用户输入,两个行为可以同时进行。请参考下面代码:
+ 案例
* 1.go
* 1.go

这个例子中,Go 程序在启动时,运行时(runtime)会默认为 main() 函数创建一个 goroutine。在 main() 函数的 goroutine 中执行到 go running 语句时,
归属于 running() 函数的 goroutine 被创建,running() 函数开始在自己的 goroutine 中执行。
此时,main() 继续执行,两个 goroutine 通过 Go 程序的调度机制同时运作。

### 使用匿名函数创建goroutine

go 关键字后也可以为匿名函数或闭包启动 goroutine。
1) 使用匿名函数创建goroutine的格式
使用匿名函数或闭包创建 goroutine 时,除了将函数定义部分写在 go 的后面之外,还需要加上匿名函数的调用参数,格式如下:

go func( 参数列表 ){
函数体
}( 调用参数列表 )


其中:
参数列表:函数体内的参数变量列表。
函数体:匿名函数的代码。
调用参数列表:启动 goroutine 时,需要向匿名函数传递的调用参数。
2) 使用匿名函数创建goroutine的例子
在 main() 函数中创建一个匿名函数并为匿名函数启动 goroutine。匿名函数没有参数。代码将并行执行定时打印计数的效果。参见下面的代码:
+ 案例
* 2.go

提示
所有 goroutine 在 main() 函数结束时会一同结束。

goroutine 虽然类似于线程概念,但是从调度性能上没有线程细致,而细致程度取决于 Go 程序的 goroutine 调度器的实现和运行环境。

终止 goroutine 的最好方法就是自然返回 goroutine 对应的函数。虽然可以用 golang.org/x/net/context 包进行 goroutine 生命期深度控制,
但这种方法仍然处于内部试验阶段,并不是官方推荐的特性。

截止 Go 1.9 版本,暂时没有标准接口获取 goroutine 的 ID。
13 changes: 13 additions & 0 deletions Go语言并发/Go语言通道(chan)/1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"fmt"
)

func main (){
//声明通道变量 var 通道变量 chan 通道类型 声明后需要配合 make 后才能使用。 chan 类型空值是 nil。
ch1:=make(chan int) // 创建一个整型类型的通道
ch2 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct {/* 一些字段 */}
ch2:=make(chan *Equip) // 创建Equip指针类型的通道, 可以存放*Equip
}
16 changes: 16 additions & 0 deletions Go语言并发/Go语言通道(chan)/2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main


import (
"fmt"
)

func main (){
//创建以空接口通道
ch:=make(chan interface{})
// 将0放入到通道中
ch<-0
// 将hello字符串放入通道中
ch <- "hello"

}
19 changes: 19 additions & 0 deletions Go语言并发/Go语言通道(chan)/3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
//"fmt"
)

func main(){
// 创建一个整型通道
ch:=make(chan int)
// 尝试将0通过通道发送
ch<-0
}

/**
PS D:\goLang\github\golang_project\Go语言并发\Go语言通道(chan)> go run 3.go
fatal error: all goroutines are asleep - deadlock!
报错的意思是:运行时发现所有的 goroutine(包括main)都处于等待 goroutine。也就是说所有 goroutine 中的 channel 并没有形成发送和接收对应的代码。
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
如果说 goroutine 是 Go语言程序的并发体的话,那么 channels 就是它们之间的通信机制。一个 channels 是一个通信机制,
它可以让一个 goroutine 通过它给另一个 goroutine 发送值信息。每个 channel 都有一个特殊的类型,
也就是 channels 可发送数据的类型。一个可以发送 int 类型数据的 channel 一般写为 chan int。

Go语言提倡使用通信的方法代替共享内存,当一个资源需要在 goroutine 之间共享时,
通道在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。声明通道时,
需要指定将要被共享的数据的类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或者指针。


在地铁站、食堂、洗手间等公共场所人很多的情况下,大家养成了排队的习惯,目的也是避免拥挤、插队导致的低效的资源使用和交换过程。
代码与数据也是如此,多个 goroutine 为了争抢数据,势必造成执行的低效率,使用队列的方式是最高效的,channel 就是一种队列一样的结构。


### 通道的特性
Go语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。

通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。

### 声明通道类型

通道本身需要一个类型进行修饰,就像切片类型需要标识元素类型。通道的元素类型就是在其内部传输的数据类型,声明如下:
var 通道变量 chan 通道类型

通道类型:通道内的数据类型。
通道变量:保存通道的变量。

chan 类型的空值是 nil,声明后需要配合 make 后才能使用。

### 创建通道

通道是引用类型,需要使用 make 进行创建,格式如下:

通道实例 := make(chan 数据类型)

数据类型:通道内传输的元素类型。
通道实例:通过make创建的通道句柄。

请看下面的例子:
+ 案例
* 1.go

### 使用通道发送数据

通道创建后,就可以使用通道进行发送和接收操作。

1) 通道发送数据的格式

通道的发送使用特殊的操作符<-,将数据通过通道发送的格式为:
通道变量 <- 值

通道变量:通过make创建好的通道实例。

值:可以是变量、常量、表达式或者函数返回值等。值的类型必须与ch通道的元素类型一致。

2) 通过通道发送数据的例子

使用 make 创建一个通道后,就可以使用<-向通道发送数据,代码如下:
+ 案例
* 2.go

3) 发送将持续阻塞直到数据被接收

把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。
Go 程序运行时能智能地发现一些永远无法发送成功的语句并做出提示,代码如下

+ 案例
* 3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
在讲解并发概念时,总会涉及另外一个概念并行。下面让我们来了解并发和并行之间的区别。

并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。

并发不是并行。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,
而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

在很多情况下,并发的效果比并行好,因为操作系统和硬件的总资源一般很少,但能支持系统同时做很多事情。
这种“使用较少的资源做更多的事情”的哲学,也是指导 Go语言设计的哲学。

如果希望让 goroutine 并行,必须使用多于一个逻辑处理器。当有多个逻辑处理器时,调度器会将 goroutine 平等分配到每个逻辑处理器上。
这会让 goroutine 在不同的线程上运行。不过要想真的实现并行的效果,用户需要让自己的程序运行在有多个物理处理器的机器上。
否则,哪怕 Go语言运行时使用多个线程,goroutine 依然会在同一个物理处理器上并发运行,达不到并行的效果。

下图展示了在一个逻辑处理器上并发运行 goroutine 和在两个逻辑处理器上并行运行两个并发的 goroutine 之间的区别。
调度器包含一些聪明的算法,这些算法会随着 Go语言的发布被更新和改进,所以不推荐盲目修改语言运行时对逻辑处理器的默认设置。
如果真的认为修改逻辑处理器的数量可以改进性能,也可以对语言运行时的参数进行细微调整。

0 comments on commit 8478fc6

Please sign in to comment.