goroutine
goroutine是Go语言提供的一种用户态线程,有时我们也称之为协程。但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
线程和协程
- 进程:拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
- 线程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度。
- 协程 :和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
创建goroutine
创建goroutine,只需要在函数调用语句前添加go关键字。
但是如果主函数先退出,其他的goroutine也会自动退出。
如果这个函数有返回值,那么这个返回值会被丢弃。
func newTask() {
fmt.Println("new goroutine")
}
func main() {
//创建一个 goroutine,启动另外一个任务
go newTask()
fmt.Println("main goroutine exit")
}
Go并发模型
Go实现了两种并发形式。
- 多线程共享内存。其实就是Java或者C++等语言中的多线程开发。
- CSP(communicating sequential processes)并发模型。这是Go语言特有的,也是Go语言推荐的。
普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问。
CSP讲究的是“以通信的方式来共享内存”。
DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
Go的CSP并发模型,是通过goroutine
和channel
来实现的。
goroutine
是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。channel
是Go语言中各个并发结构体(goroutine
)之前的通信机制。 通俗的讲,就是各个goroutine
之间通信的”管道“,有点类似于Linux中的管道。
var ch = make(chan string)
func send() {
fmt.Println("send data to ch")
ch <- "test"
}
func recive() {
result := <-ch
fmt.Printf("get data from ch: %s", result)
}
func main() {
go recive()
go send()
time.Sleep(5 * time.Second)
}
优点
- 内存消耗更少:Goroutine所需要的内存通常只有2kb,而线程则需要1Mb
- 创建与销毁的开销更小:由于线程创建时需要向操作系统申请资源,并且在销毁时将资源归还,因此它的创建和销毁的开销比较大。相比之下,goroutine的创建和销毁是由go语言在运行时自己管理的,因此开销更低。
- 切换开销更小线程的调度方式是抢占式的,如果一个线程的执行时间超过了分配给它的时间片,就会被其它可执行的线程抢占;而goroutine的调度是协同式的,它不会直接地与操作系统内核打交道。
缺点
- 协程调度机制无法实现公平调度:因为协程的调度是非入侵式的,系统不会为他分配资源。