在编写 Socket 网络程序时,需要提前准备一个线程池为每一个 Socket 的收发包分配一个线程。开发人员需要在线程数量和 CPU 数量间建立一个对应关系,以保证每个任务能及时地被分配到 CPU 上进行处理,同时避免多个任务频繁地在线程间切换执行而损失效率。
虽然,线程池为逻辑编写者提供了线程分配的抽象机制。但是,如果面对随时随地可能发生的并发和线程处理需求,线程池就不是非常直观和方便了。能否有一种机制:使用者分配足够多的任务,系统能自动帮助使用者把任务分配到 CPU 上,让这些任务尽量并发运作。这种机制在 Go语言中被称为 goroutine。
goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理。Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。
package main
import (
"fmt"
"time"
)
//默认启动main函数都会创建一个对应的goroutine
func main() {
//使用 goroutine创建匿名函数
go func() {
var times int
for {
times++
fmt.Println("tick", times)
time.Sleep(time.Second)
}
}()
// 接受命令行输入, 不做任何事情
//如果不采用 这种方式也可以采用其他方式
//因为 golang会默认为main创建一个goroutine 当遇到 go func的时候会重新开启一个goroutine main函数的goroutine
//继续执行下去
var input string
fmt.Scanln(&input)
}
Go语言中如果两个或者多个 goroutine 在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争状态(race candition)。
竞争状态的存在是让并发程序变得复杂的地方,十分容易引起潜在问题。对一个共享资源的读和写操作必须是原子化的,换句话说,同一时刻只能有一个 goroutine 对共享资源进行读和写操作。
package main
import (
"fmt"
"runtime"
"sync"
)
var (
// counter 是所有goroutine 都要增加其值的变量
counter int
// wg 用来等待程序结束
wg sync.WaitGroup
)
// main 是所有Go 程序的入口
func main() {
// 计数加 2,表示要等待两个goroutine
wg.Add(2)
// 创建两个goroutine
go incCounter()
go incCounter()
// 等待 goroutine 结束
wg.Wait()
fmt.Println("Final Counter:", counter)
}
// incCounter 增加包里counter 变量的值
func incCounter() {
// 在函数退出时调用Done 来通知main 函数工作已经完成
defer wg.Done()
for count := 0; count < 2; count++ {
// 捕获 counter 的值
value := counter
// 当前 goroutine 从线程退出,并放回到队列
runtime.Gosched()
// 增加本地value 变量的值
value++
// 将该值保存回counter
counter = value
}
}
最后输出结果值为2 多次运行的结果也可能会出现 3 4
原因是每个goroutine 线程都会覆盖前一个goroutine所做的操作,也就是说counter每次只能由一个线程操作
传统开发者模式需要维护线程池与CPU的关系 ,Golang当中也提供支持。
采用 如下设置多核运行 runtime.NumCPU获取 当前的CPU数量
runtime.GOMAXPROCS(runtime.NumCPU())
有疑问加站长微信联系(非本文作者)