Golang语言goroutine协程并发安全及锁机制

简介: 这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。

                                              作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.多协程操作同一数据问题引出

package main

import (
    "fmt"
    "sync"
)

var (
    count int
    wg    sync.WaitGroup
)

func add() {
    defer wg.Done()

    for i := 1; i <= 1000000; i++ {
        count++
    }
}

func sub() {
    defer wg.Done()

    for i := 1; i <= 1000000; i++ {
        count--
    }
}

func main() {
    // 开启2个协程等待
    wg.Add(2)

    go add()
    go sub()

    wg.Wait()

    // 在理论上,这个count结果应该是0,无论协程怎么交替执行,最终咱们想象的结果就是0,但事实上并不是!!!
    // 那为什么结果不为0呢?可以参考上图的流程,最终值不为0的执行思路。
    fmt.Printf("count = %d\n", count)
}

二.互斥锁Mutex

1 互斥锁概述

方法名 功能
func (m *Mutex) Lock() 获取互斥锁
func (m *Mutex) Unlock() 释放互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个goroutine可以访问共享资源。

Go语言中使用sync包中提供的Mutex类型来实现互斥锁。

使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁。

当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。

2使用互斥锁Mutex同步协程

package main

import (
    "fmt"
    "sync"
)

var (
    count int
    wg    sync.WaitGroup
    /*
    互斥锁:
        "sync.Mutex"为互斥锁,"Lock()"进行加锁,"Unlock()"进行解锁。

        使用"Lock()"加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁。

        互斥锁适用于读写不确定场景,即读写次数没有明显的区别,性能相对来说比较低(因为同一个时刻仅有一个协程可以操作)。
    */
    lock sync.Mutex
)

func add() {
    defer wg.Done()

    for i := 1; i <= 1000000; i++ {
        // 加锁,确保一个协程在执行逻辑的时候另外的协程不被执行。
        lock.Lock()
        count++
        // 解锁
        lock.Unlock()
    }
}

func sub() {
    defer wg.Done()

    for i := 1; i <= 1000000; i++ {
        // 加锁
        lock.Lock()
        count--
        // 解锁
        lock.Unlock()
    }
}

func main() {
    // 开启2个协程等待
    wg.Add(2)

    go add()
    go sub()

    wg.Wait()

    // 使用互斥锁同步协程,从而解决多协程在对同一个资源处理时数据同步的问题。
    fmt.Printf("count = %d\n", count)
}

三.读写互斥锁RWMutex

1 读写互斥锁概述

方法名 功能
func (rw *RWMutex) Lock() 获取写锁
func (rw *RWMutex) Unlock() 释放写锁
func (rw *RWMutex) RLock() 获取读锁
func (rw *RWMutex) RUnlock() 释放读锁
func (rw *RWMutex) RLocker() Locker 返回一个实现Locker接口的读写锁
互斥锁是完全互斥的,但是实际上有很多场景是读多写少的,当我们并发的去读取一个资源而不涉及资源修改的时候是没有必要加互斥锁的。

这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

sync.RWMutex提供了如上表所示的5个方法。读写锁分为两种:读锁和写锁。

当一个goroutine获取到读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待。

而当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

2 读写锁RWMutex引入

package main

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

var (
    /*
        读写锁:
            RWMutex是一个读写锁,其经常用于读次数远远多于写次数的场景。

            读写锁的好处是多个协程同时在读的时候,数据之间不产生影响,锁不产生影响。

            但多个协程出现了同时读和写的情况下才会产生影响,锁就会产生影响。
    */
    lock sync.RWMutex

    wg sync.WaitGroup
)

func read(id int) {
    defer wg.Done()

    // 加读锁,如果只是读数据,那么这个锁不产生影响,但是读写同时发生的时候,就会有影响
    lock.RLock()

    fmt.Printf("开始读取数据...ID = %d\n", id)
    time.Sleep(time.Second * 5)
    fmt.Printf("读取数据完成...ID = %d\n", id)

    // 解读锁
    lock.RUnlock()

}

func write() {
    defer wg.Done()

    lock.Lock()
    fmt.Printf("开始修改数据...\n")
    time.Sleep(time.Second * 3)
    fmt.Printf("修改数据完成...\n")
    lock.Unlock()

}

func main() {
    wg.Add(11)

    // 启动协程: 模拟"读多写少"场景,10个线程读(无视锁),总耗时仅需5秒。
    for i := 1; i <= 10; i++ {
        go read(i)
    }

    go write()

    wg.Wait()

    fmt.Println("main程序运行完成,程序已退出!")
}
目录
相关文章
|
1月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
53 4
Golang语言之管道channel快速入门篇
|
1月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
53 3
Golang语言之gRPC程序设计示例
|
26天前
|
Kotlin
Kotlin协程的取消机制:深入理解和优雅实现
本文详细探讨了Kotlin协程的取消机制,介绍了除直接使用`Job`的`cancel`方法外的多种优雅实现策略,如`CompletableDeferred`、`isActive`检查、`ensureActive`、`yield`及`CoroutineScope`的取消等。通过这些方法,可以更好地管理协程生命周期,确保资源正确释放,提升代码健壮性和可维护性。
54 12
|
1月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
25 3
Golang语言之Prometheus的日志模块使用案例
|
4月前
|
Go Python
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
41 1
使用python实现一个用户态协程
|
11天前
|
调度 Python
python3 协程实战(python3经典编程案例)
该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。
11 0
|
3月前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
【7月更文挑战第15天】Python的协程与异步函数优化Web性能,通过非阻塞I/O提升并发处理能力。使用aiohttp库构建异步服务器,示例代码展示如何处理GET请求。异步处理减少资源消耗,提高响应速度和吞吐量,适用于高并发场景。掌握这项技术对提升Web应用性能至关重要。
75 10
|
3月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
43 6
|
3月前
|
大数据 数据处理 API
性能飞跃:Python协程与异步函数在数据处理中的高效应用
【7月更文挑战第15天】在大数据时代,Python的协程和异步函数解决了同步编程的性能瓶颈问题。同步编程在处理I/O密集型任务时效率低下,而Python的`asyncio`库支持的异步编程利用协程实现并发,通过`async def`和`await`避免了不必要的等待,提升了CPU利用率。例如,从多个API获取数据,异步方式使用`aiohttp`并发请求,显著提高了效率。掌握异步编程对于高效处理大规模数据至关重要。
46 4
|
3月前
|
设计模式 机器学习/深度学习 测试技术
设计模式转型:从传统同步到Python协程异步编程的实践与思考
【7月更文挑战第15天】探索从同步到Python协程异步编程的转变,异步处理I/O密集型任务提升效率。async/await关键词定义异步函数,asyncio库管理事件循环。面对挑战,如思维转变、错误处理和调试,可通过逐步迁移、学习资源、编写测试和使用辅助库来适应。通过实践和学习,开发者能有效优化性能和响应速度。
47 3