Derick
913 words
5 minutes
Go语言教程:并发与互斥锁

本文将通过一个示例代码,详细讲解Go语言中的并发编程和互斥锁的使用。示例代码展示了如何使用goroutine进行并发操作,以及如何使用互斥锁保护共享资源。

示例代码解析#

导入包#

首先,我们需要导入一些必要的包:

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"sync"
)

  • bytes:提供了缓冲区操作。
  • flag:用于解析命令行参数。
  • fmt:格式化I/O。
  • io:基本的I/O接口。
  • ioutil:I/O工具函数。
  • log:日志记录。
  • sync:并发原语,包括互斥锁。

全局变量#

var protecting uint

protecting变量用于指示是否使用互斥锁来保护数据写入。值为0表示不使用,值大于0表示使用。

初始化函数#

func init() {
	flag.UintVar(&protecting, "protecting", 1, "It indicates whether to use a mutex to protect data writing.")
}

init函数用于初始化命令行参数protecting,默认值为1。

主函数#

func main() {
	flag.Parse()
	var buffer bytes.Buffer

	const (
		max1 = 5  // 启用的goroutine数量
		max2 = 10 // 每个goroutine写入的数据块数量
		max3 = 10 // 每个数据块中重复的数字数量
	)

	var mu sync.Mutex
	sign := make(chan struct{}, max1)

	for i := 1; i <= max1; i++ {
		go func(id int, writer io.Writer) {
			defer func() {
				sign <- struct{}{}
			}()
			for j := 1; j <= max2; j++ {
				header := fmt.Sprintf("\\n[id: %d, iteration: %d]", id, j)
				data := fmt.Sprintf(" %d", id*j)
				if protecting > 0 {
					mu.Lock()
				}
				_, err := writer.Write([]byte(header))
				if err != nil {
					log.Printf("error: %s [%d]", err, id)
				}
				for k := 0; k < max3; k++ {
					_, err := writer.Write([]byte(data))
					if err != nil {
						log.Printf("error: %s [%d]", err, id)
					}
				}
				if protecting > 0 {
					mu.Unlock()
				}
			}
		}(i, &buffer)
	}

	for i := 0; i < max1; i++ {
		<-sign
	}
	data, err := ioutil.ReadAll(&buffer)
	if err != nil {
		log.Fatalf("fatal error: %s", err)
	}
	log.Printf("The contents:\\n%s", data)
}

代码详解#

  1. 解析命令行参数

    flag.Parse()
    
    
  2. 定义缓冲区

    var buffer bytes.Buffer
    
    
  3. 常量定义

    const (
        max1 = 5  // 启用的goroutine数量
        max2 = 10 // 每个goroutine写入的数据块数量
        max3 = 10 // 每个数据块中重复的数字数量
    )
    
    
  4. 互斥锁和信号通道

    var mu sync.Mutex
    sign := make(chan struct{}, max1)
    
    
  5. 启动goroutine

    for i := 1; i <= max1; i++ {
        go func(id int, writer io.Writer) {
            defer func() {
                sign <- struct{}{}
            }()
            for j := 1; j <= max2; j++ {
                header := fmt.Sprintf("\\n[id: %d, iteration: %d]", id, j)
                data := fmt.Sprintf(" %d", id*j)
                if protecting > 0 {
                    mu.Lock()
                }
                _, err := writer.Write([]byte(header))
                if err != nil {
                    log.Printf("error: %s [%d]", err, id)
                }
                for k := 0; k < max3; k++ {
                    _, err := writer.Write([]byte(data))
                    if err != nil {
                        log.Printf("error: %s [%d]", err, id)
                    }
                }
                if protecting > 0 {
                    mu.Unlock()
                }
            }
        }(i, &buffer)
    }
    
    
  6. 等待所有goroutine完成

    for i := 0; i < max1; i++ {
        <-sign
    }
    
    
  7. 读取并打印缓冲区内容

    data, err := ioutil.ReadAll(&buffer)
    if err != nil {
        log.Fatalf("fatal error: %s", err)
    }
    log.Printf("The contents:\\n%s", data)
    
    

互斥锁的作用#

在并发编程中,多个goroutine可能会同时访问共享资源,导致数据竞争。互斥锁(sync.Mutex)用于确保同一时间只有一个goroutine可以访问共享资源,从而避免数据竞争。

在本示例中,如果protecting大于0,则会使用互斥锁保护数据写入操作:

if protecting > 0 {
    mu.Lock()
}
_, err := writer.Write([]byte(header))
if err != nil {
    log.Printf("error: %s [%d]", err, id)
}
for k := 0; k < max3; k++ {
    _, err := writer.Write([]byte(data))
    if err != nil {
        log.Printf("error: %s [%d]", err, id)
    }
}
if protecting > 0 {
    mu.Unlock()
}

举一反三#

通过本示例,我们可以举一反三,理解并应用以下概念:

  1. 并发编程:使用goroutine实现并发操作。
  2. 互斥锁:使用sync.Mutex保护共享资源,避免数据竞争。
  3. 信号通道:使用通道(chan)实现goroutine间的同步。

希望通过本教程,您能更好地理解Go语言中的并发编程和互斥锁的使用。祝您编程愉快!

Go语言教程:并发与互斥锁
https://blog.ithuo.net/posts/go-concurrency-and-mutex-tutorial/
Author
Derick
Published at
2022-05-23