goroutine
以下引于 claude.ai
goroutine是Go语言中的轻量级线程。具体来说,goroutine有以下几个主要特征:
- goroutine由Go运行时(runtime)管理,可以实现高度的并发。一个程序可以同时运行成千上万个goroutine。
- goroutine之间通过channel进行通信。channel使得不同goroutine之间可以安全地共享内存。
- 创建goroutine的代价非常小。因为goroutine的堆栈起始很小(仅2KB),所以可以很容易地创建很多goroutine。
- goroutine使用协作式调度(cooperative scheduling),多个goroutine可以在一个或几个操作系统线程上执行。Go运行时内部使用自举调度器在goroutine之间快速切换。
- goroutine通信安全。通信和内存同步都隐式实现,不需加锁。 总之,goroutine作为Go并发的核心,使得编写并发程序变得简单、高效。它独特的设计对Go语言的高并发性能起到关键作用。
创建goroutine
go
,只要在函数前添加它,就实现了并发。
package main
import (
"fmt"
"time"
)
func SubTask() {
for {
fmt.Println(time.Now().Format("15:04:05"), "this is a SubTask")
time.Sleep(5 * 100 * time.Millisecond) // 延迟0.5s
}
}
func main() {
go SubTask() //创建一个协程
time.Sleep(time.Second) // 延迟1s
fmt.Println(time.Now().Format("15:04:05"), "this is a main goroutine")
}
这里就可以到一个问题,虽然SubTask
是个循环,但是main
执行完毕的时候,它也就结束了。所以这里需要注意,当主协程结束的时候,子协程也会退出。那我们做并行的时候,一定要注意这种关系
如何保证子协程执行完毕,主协程再退出呢?
runtime
Gosched
以下引于 claude.ai
gosched()是一个Go运行时(runtime)提供的函数,它允许当前goroutine主动放弃CPU,将CPU时间片让给其他可运行的goroutine。
具体来说,gosched()函数有以下几个主要作用:
- 将CPU时间片让给同一物理线程上的其他goroutine运行。因为Go运行时会对goroutine进行合理调度,所以一个线程上可能有多个goroutine在运行。gosched()可以在适当的时候放弃时间片,避免goroutine独占CPU。
- 当缺少goroutine可运行时,它允许运行时内核线程安全地寻找其他物理线程上的可运行goroutine。如果找到,可以提前恢复已阻塞的goroutine,并为其分配CPU时间。
- 在多核环境下,它也可以触发work-stealing算法,让处于空闲的线程帮助正在执行的线程,提高多核CPU的利用率。
- 最后,gosched()还可以作为runtime内部算法的一部分,帮助对goroutine、线程进行优化管理。 所以,gosched()对Go并发程序的性能优化是非常重要的。使用它可以达到更高的CPU利用率,最小化线程阻塞,优化goroutine执行。
package main
import (
"fmt"
)
func main() {
go func() {
for i := 0; i < 3; i++ {
fmt.Println("goroutine...")
}
}()
for i := 0; i < 2; i++ {
//runtime.Gosched()
fmt.Println("hello main")
}
}
子协程要么没执行,要么顺序是乱的
按预期执行
Goexit
以下引于 claude.ai
goexit是Go语言runtime包中提供的一个函数,它可以立即终止当前goroutine的执行,但是defer函数会正常执行。
具体来说,goexit函数有以下几个重要作用:
- 立即终止当前goroutine,抛弃当前goroutine所占用的资源。
- 正常执行defer指定的函数。defer函数会按照逆序执行并退出,资源正常释放。
- 直接终止goroutine,不会造成阻塞。如果只调用return,仍需等待函数返回。
- 终止隐式创建的goroutine。一些操作如go语句、通道操作会隐式创建goroutine。goexit可以安全终止它们。
- 在一些阻塞的场景下,可以使用goexit完全终止goroutine,如死锁场景。
- 终止goroutine时,退出执行而不影响程序其它goroutine的执行。 总之,goexit可以强制但优雅地终止当前goroutine。它通常在goroutine中使用,特别是在出现致命错误需要立即退出,或者隐式goroutine需要立即停止的场景下使用。
package main
import (
"fmt"
"runtime"
)
func test() {
defer fmt.Println("333")
runtime.Goexit() //表示终止所在的协程
fmt.Println("444")
}
func main() {
//创建新的协程
go func() {
fmt.Println("111")
//调用别的函数
test()
fmt.Println("222")
}()
for {
}
}
GOMAXPROCS
以下引于 claude.ai
GOMAXPROCS是Go语言中的一个重要环境变量,它控制Go程序可以同时使用的CPU逻辑核心数。
具体来说,GOMAXPROCS主要有以下几个作用:
它设置可同时执行Go代码的OS线程数。默认值是机器上的CPU核心数。
通过将其设置为1,可以限制Go程序只使用1个OS线程,用于调试。
在多核环境下,适当增加GOMAXPROCS可以使Go程序利用多核心,提高并发性能。比如设置为CPU核心数的50%~80%。
GOMAXPROCS也影响Go的并行GC和调度器,增加GOMAXPROCS需要相应调整运行时参数。
在容器、serverless等环境下,合理设置GOMAXPROCS可以限制Go程序占用的CPU资源。
总之,GOMAXPROCS能够合理限制和利用Go程序的并发性。但设置不当也会适得其反,需要根据具体场景选择合适的值。适当增加GOMAXPROCS可以提高CPU利用率,但超过物理核心数效果不大。
package main
import (
"fmt"
"runtime"
)
func main() {
n := runtime.GOMAXPROCS(1) //指定以单核运算
fmt.Println("n = ", n)
for {
go fmt.Print(1)
fmt.Print(0)
}
}
单核最多只有一个goroutine,所以就是000...111...000这样的循环打印。多核就是交替打印
sync.WaitGroup
以下引于 claude.ai
sync.WaitGroup是Go语言中的一个同步原语,它可以用于等待一组goroutine都完成执行。
具体来说,sync.WaitGroup有以下几个主要用法:
在开始执行一组goroutine时,调用Add添加计数。
每个goroutine执行完成后,调用Done来减少计数。
主goroutine调用Wait方法来等待计数减为0,则表示所有goroutine都执行完成。
可以使用counters来获取当前的计数值。
WaitGroup重用需要调用Reset方法重置计数值为0。
WaitGroup使用一个计数器和cond来实现阻塞等待,以优雅地同步多个goroutine。
WaitGroup可用于生产者-消费者队列,确保完全处理完成。
也可以用来等待直到关闭通道,等待完成操作的超时等场景。
总之,WaitGroup是一个非常有用的同步工具,可以方便协调goroutine,实现阻塞直到指定的一组goroutine都结束执行。
//UpLoadFile 将整个目录都上传,遍历所有文件夹及子文件夹上传
func (c *MinioClient) UpLoadFile() {
bucketName := os.Getenv("MINIO_BUCKET_NAME") //加判断
if bucketName == "" {
panic("MINIO_BUCKET_NAME is empty")
}
var files []string
// 下载路径和bucket路径保持一致
err := filepath.Walk(bucketName, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
}
return nil
})
if err != nil {
panic(err)
}
var wg sync.WaitGroup
wg.Add(len(files))
log.Println("请稍等,执行中....")
for k, file := range files {
fileID := k
go func(filePath string) {
defer wg.Done()
objectName := strings.Replace(filePath, bucketName, "", -1)
// 检查对象是否存在
objectInfo, _ := c.Conn.StatObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
if objectInfo.Key == "" {
log.Println(fileID, filePath, objectName)
_, err := c.Conn.FPutObject(context.Background(), bucketName, objectName, filePath, minio.PutObjectOptions{
ContentType: "",
})
if err != nil {
panic(err)
}
}
}(file)
}
wg.Wait()
log.Println("Done")
return
}
这个是写的Minio的初始化工具,用来实现文件类的离线部署和自动化交付,中间就使用了WaitGroup,减少文件导入的时间