Day 6: 高并发掌控 - WaitGroup/Mutex高级同步模式

Day 6: 高并发掌控 - WaitGroup/Mutex高级同步模式

Go语言的并发编程不仅仅局限于基础的Goroutine和Channel,随着并发任务复杂度的增加,开发者常常需要处理更为复杂的同步与协调机制。本篇博客将深入探讨Go中的并发高级模式,重点介绍WaitGroupMutex同步、Context机制(超时控制与取消)以及原子操作(sync/atomic)。最后,我们将通过一个实际的例子——并发爬虫框架的雏形,来展示如何应用这些高级并发模式。

1. WaitGroup与Mutex同步

1.1 WaitGroup的使用

sync.WaitGroup用于等待一组Goroutine完成,它提供了一个简单的机制来协调多个Goroutine的同步。在并发编程中,常常需要等待多个并发任务执行完毕,WaitGroup正是为了解决这一问题。

示例代码:
package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 表示任务完成时,调用Done
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(2 * time.Second) // 模拟任务执行
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	// 启动3个工作Goroutine
	for i := 1; i <= 3; i++ {
		wg.Add(1) // 为每个Goroutine增加一个计数
		go worker(i, &wg)
	}

	// 等待所有工作Goroutine完成
	wg.Wait()
	fmt.Println("All workers are done")
}
结果:
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 1 done
Worker 2 done
Worker 3 done
All workers are done

在这个例子中,WaitGroupAdd(1)方法在每个Goroutine启动时调用,而Done()方法在每个Goroutine完成时调用。Wait()方法会阻塞主Goroutine,直到所有Goroutine完成。

1.2 Mutex的使用

sync.Mutex是一种用于保护共享资源的同步原语。它允许多个Goroutine并发访问共享资源,但在同一时刻只有一个Goroutine能够持有锁。

示例代码:
package main

import (
	"fmt"
	"sync"
)

var counter int
var mutex sync.Mutex

func increment(wg *sync.WaitGroup) {
	defer wg.Done()
	mutex.Lock()         // 获取锁
	counter++
	mutex.Unlock()       // 释放锁
}

func main() {
	var wg sync.WaitGroup

	// 启动100个并发Goroutine
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go increment(&wg)
	}

	wg.Wait()
	fmt.Println("Final counter:", counter)
}
结果:
Final counter: 100

在这个例子中,Mutex确保了只有一个Goroutine能够同时修改counter变量。即使有100个并发任务,Mutex也保证了对counter的访问是安全的。

2. Context机制(超时控制与取消)

2.1 Context的概述

Context是Go语言中用来管理并发任务的超时、取消信号以及其他请求范围内的共享值的机制。在复杂的并发程序中,特别是在网络请求和数据库查询等操作中,Context可以帮助我们控制任务的超时和取消。

示例代码:
package main

import (
	"context"
	"fmt"
	"time"
)

func longRunningTask(ctx context.Context) {
	select {
	case <-time.After(5 * time.Second):
		fmt.Println("Task completed")
	case <-ctx.Done():
		fmt.Println("Task cancelled:", ctx.Err())
	}
}

func main() {
	// 创建一个带超时的Context
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	// 启动一个长时间运行的任务
	go longRunningTask(ctx)

	// 等待任务完成或超时
	time.Sleep(4 * time.Second)
}
结果:
Task cancelled: context deadline exceeded

在这个例子中,WithTimeout方法为Context设置了一个3秒的超时。由于longRunningTask需要5秒钟才能完成,因此会被Context的超时机制取消,并输出“Task cancelled: context deadline exceeded”。

2.2 使用Context取消任务

Context还可以通过调用cancel()来显式取消任务。例如:

package main

import (
	"context"
	"fmt"
	"time"
)

func taskWithCancel(ctx context.Context) {
	select {
	case <-time.After(5 * time.Second):
		fmt.Println("Task completed")
	case <-ctx.Done():
		fmt.Println("Task cancelled:", ctx.Err())
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	// 启动任务
	go taskWithCancel(ctx)

	// 模拟取消操作
	time.Sleep(2 * time.Second)
	cancel()

	// 等待任务取消
	time.Sleep(1 * time.Second)
}
结果:
Task cancelled: context canceled

在这个例子中,cancel()被调用后,taskWithCancel任务会被取消,输出Task cancelled: context canceled

3. 原子操作(sync/atomic)

sync/atomic提供了一些原子操作,这些操作在并发环境下是安全的,能够避免竞态条件。sync/atomic主要用于对简单类型(如int32int64)进行原子加法、比较和交换等操作。

3.1 原子加法操作

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

func main() {
	var counter int32

	// 启动100个并发Goroutine,进行原子加法操作
	for i := 0; i < 100; i++ {
		go func() {
			atomic.AddInt32(&counter, 1)
		}()
	}

	// 等待足够的时间,让所有Goroutine执行完
	time.Sleep(1 * time.Second)

	// 打印结果
	fmt.Println("Final counter:", counter)
}
结果:
Final counter: 100

atomic.AddInt32确保了对counter的每次加法操作是原子的,避免了竞态条件。

4. 实战代码:并发爬虫框架雏形

在本节中,我们将应用上述并发高级模式来实现一个简单的并发爬虫框架。爬虫会从多个网站并发地获取页面内容,并输出结果。

4.1 爬虫框架设计

爬虫的任务是并发获取多个URL的内容。我们会使用WaitGroup来等待所有任务完成,Mutex来同步输出结果,Context来控制超时或取消操作。

示例代码:
package main

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"
)

var mutex sync.Mutex

func fetchURL(ctx context.Context, url string, wg *sync.WaitGroup) {
	defer wg.Done()

	// 设置超时
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		fmt.Println("Error creating request:", err)
		return
	}

	// 发送请求
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Println("Error fetching URL:", err)
		return
	}
	defer resp.Body.Close()

	// 同步输出
	mutex.Lock()
	fmt.Printf("Fetched URL: %s, Status: %s\n", url, resp.Status)
	mutex.Unlock()
}

func main() {
	urls := []string{
		"https://www.example.com",
		"https://www.google.com",
		"https://www.github.com",
	}

	// 创建带超时的Context
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	var wg sync.WaitGroup

	// 启动爬虫任务
	for _, url := range urls {
		wg.Add(1)
		go fetchURL(ctx, url, &wg)
	}

	// 等待所有任务完成
	wg.Wait()
}
结果:
Fetched URL: https://www.example.com, Status: 200 OK
Fetched URL: https://www.google.com, Status: 200 OK
Fetched URL: https://www.github.com, Status: 200 OK

在这个爬虫框架中,WaitGroup确保我们等待所有URL的请求完成,Mutex保证多个Goroutine对fmt的并发访问不会发生竞态,Context实现了超时控制。

结语

通过本篇博客,我们学习了Go语言中的并发高级模式,包括WaitGroupMutexContext和原子操作的使用。通过结合这些高级并发模式,我们实现了一个简单的并发爬虫框架,展示了如何在实际应用中处理并发任务的同步、超时控制和取消。掌握这些技巧后,你可以在实际项目中更高效地处理并发问题,编写出更稳定、高效的并发代码。

### Transformer 模型详解 #### 一、Transformer 整体架构 Transformer 是一种基于自注意力机制(Self-Attention Mechanism)的神经网络模型,旨在解决序列数据处理中的长期依赖问题。该模型摒弃了传统的循环神经网络(RNN) 和卷积神经网络(CNN),完全依靠自注意力机制来捕捉输入和输出之间的全局依赖关系[^1]。 整个架构由编码器(Encoder)和解码器(Decoder)两部分组成: - **编码器**:负责接收输入序列并将其转换成高维向量表示; - **解码器**:根据编码器产生的上下文信息生成目标序列; 两者之间通过多头自注意层(Multi-head Self-Attention Layer)连接,在每一层内部还包含了前馈神经网络(Feed Forward Neural Network, FFN)[^2]。 ```mermaid graph LR; A[Input Sequence] --> B{Encoder Stack}; subgraph Encoder Layers C[MHSA (Multi Head Self Attention)] --- D[Add & Norm]; E[FFN (Feed Forward Networks)] --- F[Add & Norm]; end G{Decoder Stack} <-- H[Memory from Encoders]; I[Output Sequence] <-- J{Decoder Layers} ``` #### 二、工作流程解析 当给定一个源语言句子作为输入时,经过分词后得到一系列token组成的列表。这些tokens会被映射到对应的嵌入(embedding)空间中形成矩阵形式的数据。随后进入多个相同的编码单元堆叠而成的编码栈内进行特征提取操作。每个编码单元主要包含两个子模块——一个多头自关注层用于计算query(Q), key(K), value(V)三者间的相似度得分,并据此调整value权重获得新的context vector; 另一个是全连接前馈网络用来进一步变换维度大小以便更好地表达语义信息。 对于翻译任务而言,则需额外构建一组类似的解码组件以逐步预测下一个可能的目标单词直至结束符为止。值得注意的是,在训练阶段为了加速收敛速度通常会采用teacher forcing技术即利用真实的上一步骤输出而非当前时刻所估计的结果参与后续迭代更新过程。 #### 三、核心特性阐述 ##### 自注意力机制 这是Transformer区别于其他传统RNN/CNN的最大亮点之一。它允许模型在同一时间步长下同时考虑所有位置的信息而不仅仅是相邻几个节点的影响范围。具体实现方式就是让每一个position都能与其他任意一处建立联系并通过softmax函数规范化后的概率分布加权求和最终得出综合考量过全部因素的新状态描述。 ##### 多头设计 考虑到单一head可能会丢失某些重要的局部模式匹配机会因此引入了multi-head策略使得不同heads可以专注于特定类型的关联性挖掘从而提高整体表现力。简单来说就是在同一层次里平行运行若干组独立却又相互补充的小规模self-attention units然后把它们各自的输出拼接起来再送往下一层继续加工处理直到最后一刻才汇总输出最终结果。 ##### 前馈神经网络 除了上述提到的核心部件之外每层还会配备有一个简单的线性变换+ReLU激活构成的标准MLP结构充当非线性的引入手段增强系统的表征能力同时也起到一定的正则化作用防止过拟合现象发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值