前言
本文所说的线程指的是Go语言的goroutine,也就是协程。
多线程
Go语言天生支持多线程编程,Go语言的goroutine是一种轻量级线程实现,可以在同一个进程中并发执行多个任务,同时又能保证数据安全。
启动一个goroutine很简单,只需要在函数前加上关键字go,就可以让这个函数在一个新的goroutine中运行。下面是一个简单的例子:
1 | func main() { |
上面的代码会输出:
1 | Hello from main |
注意到输出的顺序可能不是按照代码的顺序来的,因为两个goroutine是并发执行的。
Go语言还提供了一些同步机制,如channel、锁等,来保证多个goroutine之间的协调和同步。这些机制在多线程编程中非常重要,可以避免数据竞争等问题,保证程序的正确性和可靠性。
延迟执行
在Go语言中,可以使用时间.After和定时器来延迟执行函数。
时间.After会在指定时间后发送当前时间到返回的channel中,我们可以使用它来延迟执行函数:
1 | go func() { |
5秒后,会在新的goroutine中调用sayHello函数。
使用定时器,我们可以重复延迟执行函数:
1 | timer := time.NewTimer(5 * time.Second) |
定时器的C channel会在定时时间后发送当前时间,我们通过channel接收时间,执行函数,然后重置定时器,这样就实现了重复延迟执行。
另外,我们也可以使用time.Tick实现重复延迟:
1 | ticker := time.NewTicker(5 * time.Second) |
time.Tick会每隔一定时间就发送当前时间到返回的channel中。
综上,Go语言提供了三种延迟执行函数的方法:
- time.After: 单次延迟,在指定时间后执行函数
- 定时器:可以重复延迟执行函数
- time.Tick: 可以按固定时间间隔重复执行函数使用这些方法,我们可以在Go语言中实现各种延迟调度和定时任务。
线程安全
多线程操作切片
在 Go 语言中,可以通过 goroutine 来实现多线程操作同一个切片。
但需要注意的是,在多个 goroutine 中同时操作同一个切片时,可能会出现竞争条件(race condition),因此需要使用互斥锁(Mutex)来保护共享资源,以避免数据竞争问题。
主要使用Mutex实现
1 | var mutex sync.Mutex |
以下是一个示例代码,演示如何在多个 goroutine 中操作同一个切片并使用互斥锁保护:
1 | package main |
在上面的示例中,我们定义了一个切片 slice
和一个互斥锁 mutex
,然后启动了 5 个 goroutine 并发地向切片中追加数据。
在每个 goroutine 中对切片的修改都需要先获取互斥锁,然后在操作完成后释放互斥锁。
最后等待所有 goroutine 执行完成后输出最终的切片内容。
线程池
在 Go 语言中,可以通过使用 goroutine 和 channel 来实现线程池的功能。
线程池可以帮助有效地管理 goroutine 的数量,避免无限制地启动大量的 goroutine,从而提高系统的性能和稳定性。
自定义线程池
1 | package utils |
使用
1 | package test |
chan
在 Go 语言中,chan
是用于在不同 goroutine 之间进行通信的一种数据结构。
chan
(通道)并不完全是一个队列,但是它可以用于实现队列的功能,但它更广泛地用于在 goroutine
之间安全地传递数据。
chan
可以用来在 goroutine 之间传递数据,实现协程之间的同步和通信。
在 Go 中,使用 make
函数可以创建一个 chan
,语法如下:
1 | ch := make(chan int) |
上面的代码创建了一个 chan
,这个 chan
只能传递整数类型的数据。
你也可以创建传递其他类型的数据,比如字符串、结构体等。
chan
可以用于发送和接收数据,发送数据可以使用 <-
操作符,接收数据也可以使用 <-
操作符。
下面是一个简单的示例:
1 | package main |
在这个示例中,我们创建了一个整数类型的 chan
,并在一个 goroutine 中向 chan
发送了数字 10,然后在主 goroutine 中接收这个数据并打印出来。
通过 chan
实现的通信机制可以帮助不同的 goroutine 之间进行数据交换和同步,是 Go 语言并发编程中的重要组成部分。