GO语言基础笔记(四):并发编程基础

目录

Goroutines

通道(Channel)

代码示例


Goroutines

  1. 定义与特点

    • Goroutines是Go语言中实现并发的基本单位。
    • 它比传统的线程更轻量级,拥有更小的内存占用和更快的启动时间。
    • 在Go程序中,您可以轻松地启动成千上万的Goroutines。
  2. 使用方法

    • 使用go关键字后跟一个函数调用,即可启动一个Goroutine。
    • 例如:go myFunction()
package main

import (
    "fmt"
    "time"
)

// 定义一个简单的函数
func printNumbers(prefix string) {
    for i := 1; i <= 5; i++ {
        fmt.Println(prefix, i)
        // 休眠一段时间,以模拟实际操作中的耗时
        time.Sleep(time.Millisecond * 500)
    }
}

func main() {
    // 使用 go 关键字启动一个新的 Goroutine
    go printNumbers("Goroutine")

    // 主Goroutine也执行相同的函数
    printNumbers("Main")

    // 等待足够长的时间以确保Goroutine完成
    // 注意:这不是同步Goroutines的推荐方式
    // 后续课程将介绍更好的方法(如WaitGroup或Channel)
    time.Sleep(time.Second * 3)

    fmt.Println("主函数执行完毕")
}
  1. 函数定义printNumbers函数接受一个字符串参数prefix,并打印出该前缀下的数值序列。
  2. 启动Goroutine:使用go关键字启动printNumbers函数作为一个新的Goroutine。这意味着printNumbers("Goroutine")将并行执行。
  3. 主Goroutine:主函数main本身也是一个Goroutine。在这里,它调用printNumbers("Main"),同样执行数值打印操作。
  4. 并行执行:您会看到,"Goroutine"和"Main"的输出是交替出现的,显示了两个Goroutine是同时运行的。
  5. 等待结束:使用time.Sleep暂时等待Goroutine完成。这是为了演示目的;实际应用中我们会使用更加精确的同步机制。
  • 在实际应用中,我们通常不使用time.Sleep来等待Goroutines完成,而是使用像通道(Channels)或sync.WaitGroup这样的同步机制。
  • 这个示例的目的是简单地展示Goroutines的并发执行。在后续课程中,我们将探讨更高级的同步技术和并发模式。

 

通道(Channel)

  1. 定义与作用

    • Channel是Go语言中的一种内置类型,用于在Goroutines之间安全地传递数据。
    • 它可以帮助解决Goroutines之间的同步问题。
  2. 类型与使用

    • 有缓冲和无缓冲两种类型的Channel。
    • 创建示例:ch := make(chan int)(无缓冲)或ch := make(chan int, 5)(有缓冲,容量为5)。
    • 数据传递:使用<-操作符向Channel发送或接收数据。

 

package main

import (
    "fmt"
    "time"
)

func main() {
    // 创建一个无缓冲的Channel
    ch := make(chan string)

    // 启动一个Goroutine,发送数据到Channel
    go func() {
        fmt.Println("Goroutine开始发送数据")
        ch <- "从Goroutine传来的消息"
    }()

    // 模拟延时,表示主Goroutine正在处理其他任务
    time.Sleep(2 * time.Second)

    // 从Channel接收数据
    message := <-ch
    fmt.Println("接收到数据:", message)

    // 创建一个有缓冲的Channel,容量为2
    bufferedCh := make(chan int, 2)

    // 向有缓冲的Channel发送数据,不会立即阻塞
    bufferedCh <- 1
    bufferedCh <- 2

    // 从有缓冲的Channel接收数据
    fmt.Println("从有缓冲Channel接收数据:", <-bufferedCh)
    fmt.Println("从有缓冲Channel接收数据:", <-bufferedCh)
}

  1. 无缓冲Channelch := make(chan string)创建了一个无缓冲的Channel,用于传递字符串类型的数据。
  2. 发送数据到Channel:Goroutine中使用ch <- "从Goroutine传来的消息"向Channel发送数据。此时,如果没有接收者,发送操作会阻塞。
  3. 接收Channel数据:主Goroutine中使用message := <-ch接收数据。接收操作会阻塞直到有数据到达。
  4. 有缓冲ChannelbufferedCh := make(chan int, 2)创建了一个容量为2的有缓冲Channel。
  5. 向有缓冲Channel发送数据:发送操作在Channel未满时不会阻塞。
  6. 从有缓冲Channel接收数据:即使发送Goroutine已经结束,数据仍然可以从Channel中被接收。

  • 无缓冲Channel在没有接收者时,发送操作会阻塞;有缓冲Channel则在缓冲区满时才会阻塞。
  • Channel的正确使用是并发编程中非常重要的部分,需要仔细处理发送和接收的同步问题。

代码示例

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// 数据处理函数
// 模拟接收一个整数,经过处理后发送到输出Channel
func processData(id int, data int, out chan<- string) {
	// 模拟数据处理耗时
	time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
	result := fmt.Sprintf("Goroutine %d 处理结果:%d", id, data*2) // 假设处理是简单的乘以2
	out <- result
}

func main() {
	// 初始化随机种子
	rand.Seed(time.Now().UnixNano())

	// 创建一个无缓冲的Channel
	dataCh := make(chan string)

	// 使用WaitGroup等待所有Goroutines完成
	var wg sync.WaitGroup

	// 启动多个Goroutines进行数据处理
	const numGoroutines = 5 // Goroutines的数量
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			data := rand.Intn(100) // 生成一个随机数作为数据
			processData(id, data, dataCh)
		}(i)
	}

	// 启动一个Goroutine用于接收所有处理结果
	go func() {
		wg.Wait()
		close(dataCh)
	}()

	// 从Channel中读取并打印每个Goroutine的处理结果
	for result := range dataCh {
		fmt.Println(result)
	}

	fmt.Println("所有Goroutine处理完成")
}
  1. processData函数:每个Goroutine都会调用这个函数,它接收一个整数数据,处理后(这里简化为乘以2的操作),将结果发送到输出Channel。

  2. 启动多个Goroutines:循环中创建了多个Goroutines,每个都调用processData函数处理数据。

  3. 使用sync.WaitGroupWaitGroup用于等待所有Goroutine完成其工作。每启动一个Goroutine,就调用wg.Add(1),每个Goroutine完成时调用wg.Done()

  4. 关闭Channel:在所有Goroutines完成后,关闭数据Channel。这是通过在另一个Goroutine中调用wg.Wait()close(dataCh)实现的。

  5. 读取Channel数据:主Goroutine循环读取Channel中的数据,并打印结果。

你可能感兴趣的:(Go,golang,笔记,开发语言)