Go协程及并发锁应用指南

news2024/11/18 3:39:17

概念

协程(Goroutine)是Go语言独有的并发体,是一种轻量级的线程,也被称为用户态线程。相对于传统的多线程编程,协程的优点在于更加轻量级,占用系统资源更少,切换上下文的速度更快,不需要像多线程编程一样处理锁等线程安全问题。
在这里插入图片描述

1. 协程的创建

在Go语言中,可以使用go语句来启动一个协程。go语句后面跟的是一个函数调用,即启动一个新协程去执行该函数。例如:

func main() {
    go printHello() // 启动一个goroutine去执行printHello函数
    fmt.Println("main function")
}

func printHello() {
    fmt.Println("hello goroutine")
}

在上面的代码中,printHello函数会在新的协程中执行,而不会阻塞主线程。

一、协程间的通信

在这里插入图片描述

1. Channel的定义和初始化

在Go语言中,协程之间的通信主要通过channel实现。使用make函数可以创建一个channel,其语法为:

channel_name := make(chan data_type)

其中,data_type为channel中传输数据的类型。例如,创建一个传输int类型数据的channel:

ch := make(chan int)
2. Channel的读写

Channel既可以进行发送操作,也可以进行接收操作。发送操作和接收操作都是阻塞的。

  • 发送操作:channel_name <- value
  • 接收操作:value := <-channel_name

二、并发锁的应用

在并发编程中,为了防止多个协程同时访问共享资源导致的数据竞争和不一致问题,需要使用锁机制来同步访问。Go语言提供了多种锁机制,包括互斥锁(Mutex)和读写锁(RWMutex)等。
在这里插入图片描述

1. 互斥锁(Mutex)

互斥锁是Go语言中最常用的锁机制,它确保同一时刻只有一个协程可以访问临界区。使用sync包中的Mutex类型来创建互斥锁,并通过Lock()Unlock()方法来加锁和解锁。

import "sync"

var mutex = &sync.Mutex{}

func criticalSection() {
    mutex.Lock()
    defer mutex.Unlock()
    // 临界区代码
}
2. 读写锁(RWMutex)

读写锁允许多个协程同时读取共享资源,但在有写操作时,需要互斥访问。使用sync包中的RWMutex类型来创建读写锁,通过RLock()RUnlock()Lock()Unlock()方法来分别获取读锁、释放读锁、获取写锁和释放写锁。

import "sync"

var rwlock = &sync.RWMutex{}

func read() {
    rwlock.RLock()
    defer rwlock.RUnlock()
    // 读取共享资源
}

func write() {
    rwlock.Lock()
    defer rwlock.Unlock()
    // 修改共享资源
}

三、注意事项

  1. 锁的粒度:应尽可能减少锁的持有时间,避免降低程序的性能。
  2. 避免死锁:在使用锁时,需要仔细设计锁的使用顺序,避免循环等待导致的死锁。
  3. 选择合适的锁:根据具体场景选择合适的锁机制,如读写锁在读多写少的场景下可以显著提高性能。

在这里插入图片描述

四、简单完整示例

当然,以下是一个包含Go协程、Channel以及互斥锁(Mutex)完整示例的程序。这个程序将演示如何使用协程来处理并发任务,并通过Channel进行协程间的通信,同时使用互斥锁来保护共享资源的数据一致性。

package main

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

// 假设有一个共享资源,我们需要保护它不被并发访问破坏
var (
	sharedResource int
	mutex          sync.Mutex
)

// 一个用于修改共享资源的函数,通过互斥锁保证数据一致性
func modifySharedResource(increment int) {
	mutex.Lock()
	defer mutex.Unlock()
	sharedResource += increment
	fmt.Printf("Modified sharedResource to %d\n", sharedResource)
}

// 一个使用协程的函数,它会多次修改共享资源并通过Channel发送完成信号
func worker(done chan bool, increment int) {
	for i := 0; i < 5; i++ {
		modifySharedResource(increment)
		time.Sleep(time.Millisecond * 100) // 模拟耗时操作
	}
	done <- true // 发送完成信号
}

func main() {
	done := make(chan bool, 2) // 创建一个带有缓冲的Channel,确保可以发送两个值而不会阻塞

	// 启动两个协程,每个协程都将尝试修改共享资源
	go worker(done, 10)
	go worker(done, 20)

	// 等待两个协程完成
	for i := 0; i < 2; i++ {
		<-done // 阻塞等待接收来自协程的完成信号
	}

	fmt.Println("All workers done. Final sharedResource:", sharedResource)
}

在这个示例中:

  1. 定义了一个全局的共享资源sharedResource和一个互斥锁mutex
  2. modifySharedResource函数用于修改共享资源,它首先加锁,修改完成后释放锁。
  3. worker函数是并发执行的函数,它接受一个done Channel和一个increment值作为参数。它会循环5次,每次循环都调用modifySharedResource来修改共享资源,并在每次修改后短暂睡眠以模拟耗时操作。循环结束后,它通过done Channel发送一个完成信号。
  4. main函数中,我们创建了一个done Channel和两个协程,每个协程以不同的increment值调用worker函数。
  5. 我们通过for循环和<-donedone Channel接收两次完成信号,确保两个协程都已完成工作。
  6. 最后,我们打印出最终的sharedResource值。

五、模拟错误收集示例

在Go语言中,协程(Goroutine)的错误处理通常是通过返回值、通道(Channel)或上下文(Context)来完成的。由于协程本质上是轻量级的线程,它们并不直接支持像传统线程那样的异常机制。不过,我们可以通过上述几种方式来实现错误传播和处理。

下面是一个扩展了之前示例的程序,其中增加了协程的错误处理。我们将使用通道(Channel)来传递错误和完成信号。

package main

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

// 假设的共享资源
var sharedResource int
var mutex sync.Mutex

// 模拟可能失败的操作
func modifySharedResource(increment int) error {
	mutex.Lock()
	defer mutex.Unlock()
	// 假设某种条件下操作失败
	if increment < 0 {
		return errors.New("negative increment is not allowed")
	}
	sharedResource += increment
	fmt.Printf("Modified sharedResource to %d\n", sharedResource)
	return nil
}

// worker 协程函数,现在可以返回错误
func worker(done chan bool, errChan chan error, increment int) {
	for i := 0; i < 5; i++ {
		if err := modifySharedResource(increment); err != nil {
			errChan <- err // 将错误发送到错误通道
			return
		}
		time.Sleep(time.Millisecond * 100) // 模拟耗时操作
	}
	done <- true // 发送完成信号
}

func main() {
	done := make(chan bool, 2)
	errChan := make(chan error, 2) // 创建一个用于传递错误的通道

	// 启动两个协程,一个可能遇到错误
	go worker(done, errChan, 10)
	go worker(done, errChan, -5)  // 这个将尝试一个负增量,应该失败

	// 等待协程完成或接收错误
	for i := 0; i < 2; i++ {
		select {
		case <-done:
			fmt.Println("Worker done")
		case err := <-errChan:
			fmt.Println("Error received:", err)
			// 根据需要处理错误,比如退出程序、记录日志等
			// 这里我们简单地打印错误并继续等待其他协程
		}
	}

	// 注意:在实际情况中,你可能需要一种方式来确保所有协程都已完成或都已报告错误
	// 这里为了简化,我们没有实现这样的逻辑

	fmt.Println("All workers potentially done or errors reported. Final sharedResource:", sharedResource)

	// 注意:如果某些协程可能永远不发送完成信号(比如因为死循环或无限等待),
	// 你可能需要一个超时机制或一种方式来优雅地关闭这些协程。
}

在这个示例中:

  • modifySharedResource 函数现在可以返回一个错误,如果增量是负数。
  • worker 函数现在接受一个额外的 errChan 通道,用于在发生错误时发送错误消息。如果发生错误,worker 函数将发送错误并立即返回。
  • main 函数中,我们使用了一个 select 语句来同时等待完成信号和错误。这允许我们在任何一个事件发生时都能立即响应。

请注意:这个示例并没有实现一个完美的错误处理策略,特别是如果某些协程可能永远不发送完成信号(比如因为死循环或无限等待)。在实际应用中,你可能需要实现更复杂的逻辑来确保程序的健壮性和可靠性。例如,你可以使用上下文(Context)来优雅地关闭或取消长时间运行的协程。

六、举一个实际应用的示例[更新购物车数量]

  1. 高效并发处理:购物车系统在高并发访问时,需要快速处理多个用户的请求。Go协程的轻量级特性使得能够轻松创建成千上万的并发任务,而不会对系统资源造成太大压力。

  2. 简化并发编程:Go的协程和通道(Channel)模型简化了并发编程的复杂度。通过通道进行协程间的通信,可以避免传统并发编程中的许多常见问题,如竞态条件、死锁等。

  3. 内置并发原语:Go标准库提供了丰富的并发原语,如互斥锁(Mutex)、读写锁(RWMutex)、条件变量(Cond)等,这些工具可以进一步帮助开发者编写安全、高效的并发代码。

  4. 易于调试和监控:Go的协程模型使得并发程序的调试和监控相对容易。通过工具如pproftrace等,开发者可以很方便地分析并发程序的性能瓶颈和潜在问题。

购物车并发处理完整示例

下面是一个简化的购物车并发处理示例,展示了如何使用Go协程和通道来处理多个用户的购物车更新请求。

package main

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

// 假设的购物车结构
type ShoppingCart struct {
	Items map[string]int
	mutex sync.Mutex
}

// 更新购物车的函数
func (c *ShoppingCart) Update(itemID string, quantity int) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.Items[itemID] += quantity
	fmt.Printf("Updated cart, item %s now has %d\n", itemID, c.Items[itemID])
}

// 模拟处理购物车更新请求的协程
func handleCartUpdate(cart *ShoppingCart, itemID string, quantity int, done chan<- bool) {
	time.Sleep(time.Millisecond * 100) // 模拟处理时间
	cart.Update(itemID, quantity)
	done <- true // 发送完成信号
}

func main() {
	cart := &ShoppingCart{Items: make(map[string]int)}
	done := make(chan bool, 10) // 假设最多同时处理10个请求

	// 模拟多个用户同时更新购物车
	for i := 0; i < 10; i++ {
		go handleCartUpdate(cart, fmt.Sprintf("item%d", i), 5, done)
	}

	// 等待所有更新完成
	for i := 0; i < 10; i++ {
		<-done
	}

	fmt.Println("All cart updates completed.")
	// 在这里可以进一步处理更新后的购物车,如发送到数据库保存等
}

在这个示例中:

  • 我们定义了一个ShoppingCart结构体,它包含一个商品项的映射和一个互斥锁来保护这个映射的并发访问。
  • Update方法用于更新购物车中的商品数量,它通过互斥锁来确保线程安全。
  • handleCartUpdate协程函数模拟了处理购物车更新请求的过程,它接收购物车、商品ID、数量和完成信号通道作为参数,并在更新完成后发送完成信号。
  • main函数中,我们创建了10个协程来模拟多个用户同时更新购物车的情况,并通过done通道等待所有更新完成。

请注意:这个示例为了简化而省略了一些重要的错误处理和优化措施,比如在实际应用中你可能需要处理网络请求、数据库操作等可能失败的情况,并且可能需要使用更复杂的并发控制策略来优化性能。

总结

通过以上指南,您可以更好地理解和应用Go协程及并发锁,在Go语言的并发编程中编写出高效、安全的代码。以上都是些简单代码片段示例,不能作为实际应用使用哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2136651.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue:使用v-model绑定的textarea在光标处插入指定文本

一、问题描述 使用v-model绑定的textarea如果需要改变其内容&#xff0c;一般只要改变v-model对应的变量即可&#xff0c;但如果需要在textarea的当前光标位置插入指定文本&#xff0c;那就需要操作DOM了。于是我们写了一段js&#xff1a; const insertTextAtCursor (text) …

聊天组件 Vue3-beautiful-chat

前言 最近很多公司都在搞大模型&#xff0c;类似于 chatgpt 的功能&#xff1b;而 chatgpt 的界面其实就是个对话框。今天就介绍一个不错的对话框组件 Vue3-beautiful-chat 项目框架 vite vue3 TS Vue3-beautiful-chat 使用流程 1、引用三方件 npm install Vue3-beaut…

【大模型专栏—进阶篇】语言模型创新大总结——“三派纷争”

大模型专栏介绍 &#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文为大模型专栏子篇&#xff0c;大模型专栏将持续更新&#xff0c;主要讲解大模型从入门到实战打怪升级。如有兴趣&#xff0c;欢迎您的阅读。 &#x1f4…

ChatGPT对话训练数据采集渠道有哪些

ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它可以生成逼真的自然语言回复&#xff0c;被广泛应用于聊天机器人、智能助理等领域。ChatGPT本身需要依赖大量的训练对话数据和算法运行&#xff0c;其所依赖的对话数据&#xff0c;需要专业的数据采集标注处理流程才…

20 递归算法精髓解析:基准、性质、案例(阶乘、斐波拉契、猴子吃桃、汉诺塔等)、与循环的对比

目录 1 概述 2 递归的基本组成部分 2.1 基准情况 2.2 递归步骤 2.3 案例&#xff1a;循环实现阶乘的计算 2.4 案例&#xff1a;递归函数实现阶乘的计算 3 递归的性质 3.1 自我调用 3.2 栈的使用 3.3 问题分解 3.4 性能考虑 3.5 案例&#xff1a;递归的回溯 4 综合…

WPF DataGrid 列表中,DataGrid.Columns 列根据不同的值显示不同内容

需求&#xff1a;在WPF DataGrid 控件中&#xff0c;有以下列&#xff0c;绑定了一个LogType&#xff0c;值分别是0,1,2&#xff0c;根据不同的值&#xff0c;显示不同的内容以及背景 <DataGrid ItemsSource"{Binding EventLog}"><DataGrid.Columns><…

力扣之1777.每家商店的产品价格

文章目录 1. 1777.每家商店的产品价格1.1 题干1.2 建表1.3 题解1.4 结果截图 1. 1777.每家商店的产品价格 1.1 题干 表&#xff1a;Products -------------------- | Column Name | Type | -------------------- | product_id | int | | store | enum | | price | int | ---…

猜数-while-python

题目要求&#xff1a; 设置一个范围1-100的随机整数变量&#xff0c;通过while循环&#xff0c;诶和input语句&#xff0c;判断输入的数字是否等于随机数 无限次机会&#xff0c;直到猜中为止每一次不猜中都&#xff0c;会提示大了小了猜完数字后&#xff0c;提示裁了几次 imp…

K8s 之Pod的定义及详细资源调用案例

资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务所谓的部署服务&#xff0c;其实就是在kubernetes集群中运行一个个的容器&a…

Day25_0.1基础学习MATLAB学习小技巧总结(25)——四维图形的可视化

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a; 1、《MATLAB基础教程 (第三版) (薛山)》 2、《MATL…

开发后台管理系统-开发环境搭建

文章目录 需求设计环境搭建创建项目工程测试结果 安装Element Plus安装路由安装Vue Router配置Vue Router 测试 需求 开发一个后台管理系统 这里以CDN后台管理系统为例 设计 参照 CDN后台管理系统功能说明文档 环境搭建 确保已经安装了Node.js和npm 执行 npm install -g vu…

【极限、数学】 NOIP 2018 提高组初赛试题 第 7 题详解(线段长度期望)

在一条长度为 1 1 1 的线段上随机取两个点&#xff0c;则以这两个点为端点的线段的期望长度是&#xff08; &#xff09;。 考虑将一个线段上平均分布有 n ( n ≥ 2 ) n(n\geq 2) n(n≥2) 个节点&#xff0c;其中首尾均有一个节点&#xff0c;那么我们就将一个线段均分为 n…

SSMP+ajax实现广告系统的分页效果

文章目录 1.案例需求2.编程思路3.案例源码4.小结 1.案例需求 使用SSMPajax实现广告系统的分页效果&#xff0c;效果图如下&#xff1a; 2.编程思路 mapper层&#xff1a;定义一个接口&#xff0c;继承自BaseMapper&#xff0c;指定泛型为AdvInfo&#xff0c;这样MyBatis Pl…

Idea springboot项目热部署

使用 spring-boot-devtools spring-boot-devtools 是 Spring Boot 提供的开发工具模块&#xff0c;它可以自动检测到代码的变化并重启应用&#xff0c;实现热部署。 配置步骤&#xff1a; 添加依赖&#xff1a; 在项目的 pom.xml 中加入 spring-boot-devtools 依赖&#xff1…

SQL Server性能优化之读写分离

理论部分: 数据库读写分离&#xff1a; 主库&#xff1a;负责数据库操作增删改 20% 多个从库&#xff1a;负责数据库查询操作 80% 读写分离的四种模式 1.快照发布&#xff1a;发布服务器按照预定的时间间隔向订阅服务器发送已发布的数据快照 2.事务发布[比较主流常见]&#xf…

roctracer 的应用示例

1&#xff0c;不用 roctracer 的普通场景 mt.cpp /* Copyright (c) 2018-2022 Advanced Micro Devices, Inc.Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software")…

哪款宠物空气净化器是除浮毛王者?希喂、范罗士、霍尼韦尔宠物空气净化器实测

养宠人绕不过的痛——掉毛&#xff01;脱毛&#xff01;又到了掉毛季&#xff0c;就连空气中都有毛毛……不管遇到谁&#xff0c;都知道你养猫养狗了——只因T恤变身毛线衫、毛毛怎么都粘不干净。不止是衣服上&#xff0c;地板上、沙发上、桌面上&#xff0c;哪哪都是毛。开始养…

从头开始学MyBatis—02基于xml和注解分别实现的增删改查

首先介绍此次使用的数据库结构&#xff0c;然后引出注意事项。 通过基于xml和基于注解的方式分别实现了增删改查&#xff0c;还有获取参数值、返回值的不同类型对比&#xff0c;帮助大家一次性掌握两种代码编写能力。 目录 数据库 数据库表 实体类 对应的实体类如下&#x…

【目标检测】labelimg图像标注软件的使用流程

一、labelimg检测图片标注 1、下载labelimg.exe 链接&#xff1a;https://pan.baidu.com/s/1yk8ff56Xu40-ZLBghEQ5nw 提取码&#xff1a;vj8f 下载的文件是编译好的&#xff0c;可执行的labelImg.exe文件。直接将文件放在windows环境下&#xff0c;双击可执行。&#xff08;如果…

典型BUCK电路学习和设计

手把手教你设计12V3Abuck降压电路-2-相关输入参数讲解_哔哩哔哩_bilibili 这里是输入电容&#xff0c;先过大电容&#xff08;电解电容&#xff09;再过小电容&#xff08;陶瓷贴片电容&#xff0c;高频率波&#xff09; 输出也可以同理 开关电源不能带负载的原因&#xff0c…