# Golang 并发编程:掌握 goroutine 和 channel
## 什么是并发编程?
并发编程是指同时执行多个任务的编程方式。在 Go 语言中,并发编程是其核心特性之一,通过 goroutine 和 channel 实现。
## goroutine
### 什么是 goroutine?
goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理。与传统线程相比,goroutine 的创建和切换成本非常低,因此可以创建大量的 goroutine。
### 创建 goroutine
“`go
// 启动一个 goroutine
func main() {
go sayHello()
fmt.Println(“Main function”)
}
func sayHello() {
fmt.Println(“Hello from goroutine”)
}
“`
### goroutine 的特性
– goroutine 是轻量级的,创建成本低
– goroutine 的调度由 Go 运行时负责
– goroutine 之间共享内存
– 当主 goroutine 结束时,所有其他 goroutine 也会结束
## channel
### 什么是 channel?
channel 是 goroutine 之间通信的管道,可以用于在 goroutine 之间传递数据。
### 创建 channel
“`go
// 创建无缓冲通道
ch := make(chan int)
// 创建带缓冲通道
ch := make(chan int, 10)
“`
### 通道的操作
#### 发送数据
“`go
ch <- 10 // 发送 10 到通道
```
#### 接收数据
```go
// 接收数据
value := <-ch
// 非阻塞接收
value, ok := <-ch
if ok {
fmt.Println("Received:", value)
} else {
fmt.Println("Channel closed")
}
```
#### 关闭通道
```go
close(ch)
```
### 通道的特性
- 通道是引用类型
- 无缓冲通道的发送和接收操作会阻塞
- 带缓冲通道在缓冲区满时发送会阻塞,在缓冲区空时接收会阻塞
- 关闭通道后,不能再发送数据,但可以继续接收数据
## 并发模式
### 等待多个 goroutine 完成
#### 使用 sync.WaitGroup
```go
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("Goroutine", i)
}(i)
}
wg.Wait()
fmt.Println("All goroutines completed")
}
```
### 单向通道
```go
// 只发送通道
type Sender chan<- int
// 只接收通道
type Receiver <-chan int
func send(s Sender) {
s <- 10
}
func receive(r Receiver) {
value := <-r
fmt.Println(value)
}
```
### 通道的选择
```go
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(1 * time.Second)
ch1 <- 1
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- 2
}()
for i := 0; i < 2; i++ {
select {
case value := <-ch1:
fmt.Println("Received from ch1:", value)
case value := <-ch2:
fmt.Println("Received from ch2:", value)
case <-time.After(3 * time.Second):
fmt.Println("Timeout")
return
}
}
}
```
## 并发安全
### 互斥锁
```go
var (
counter int
mutex sync.Mutex
)
func increment() {
mutex.Lock()
defer mutex.Unlock()
counter++
}
func getCounter() int {
mutex.Lock()
defer mutex.Unlock()
return counter
}
```
### 读写锁
```go
var (
counter int
rwMutex sync.RWMutex
)
func increment() {
rwMutex.Lock()
defer rwMutex.Unlock()
counter++
}
func getCounter() int {
rwMutex.RLock()
defer rwMutex.RUnlock()
return counter
}
```
### 原子操作
```go
var counter int32
func increment() {
atomic.AddInt32(&counter, 1)
}
func getCounter() int32 {
return atomic.LoadInt32(&counter)
}
```
## 并发最佳实践
1. **使用 goroutine 处理并发任务**:将独立的任务放在 goroutine 中执行
2. **使用 channel 进行通信**:通过 channel 在 goroutine 之间传递数据,避免共享内存
3. **使用 sync.WaitGroup 等待 goroutine 完成**:确保所有 goroutine 都完成后再继续执行
4. **避免死锁**:确保通道的发送和接收操作能够正确配对
5. **使用缓冲通道**:适当使用缓冲通道可以减少阻塞,提高性能
6. **使用 select 处理多个通道**:使用 select 语句可以同时监听多个通道的操作
7. **注意并发安全**:对共享资源的访问要使用互斥锁或原子操作
## 实例:并发下载文件
```go
func main() {
urls := []string{
"https://example.com/file1.txt",
"https://example.com/file2.txt",
"https://example.com/file3.txt",
}
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
downloadFile(url)
}(url)
}
wg.Wait()
fmt.Println("All files downloaded")
}
func downloadFile(url string) {
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error downloading", url, err)
return
}
defer resp.Body.Close()
filename := filepath.Base(url)
file, err := os.Create(filename)
if err != nil {
fmt.Println("Error creating file", err)
return
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
fmt.Println("Error copying file", err)
return
}
fmt.Println("Downloaded", url)
}
```
## 总结
本文介绍了 Go 语言中的并发编程,包括 goroutine、channel、并发模式和并发安全等内容。Go 语言的并发模型基于 CSP(Communicating Sequential Processes)理论,通过 goroutine 和 channel 实现了高效的并发编程。掌握这些并发编程技术,对于编写高性能、可扩展的 Go 应用程序至关重要。在实际开发中,我们需要根据具体场景选择合适的并发模式,确保程序的正确性和性能。