首页 > 其他分享 >Go并发相关

Go并发相关

时间:2024-06-18 20:24:13浏览次数:18  
标签:wg map func goroutine 并发 sm Go 相关 string

map是并发安全的吗?

首先我们写一段程序验证一下,创建两个goroutine,同时对一个map进行写操作,看看会发生什么吧!

func main() {
    m := make(map[string]int)
    m["foo"] = 1

    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        for i := 0; i < 1000; i++ {
            m["foo"]++
        }
        wg.Done()
    }()

    go func() {
        for i := 0; i < 1000; i++ {
            m["foo"]++
        }
        wg.Done()
    }()

    wg.Wait()
}

欸出错了!

fatal error: concurrent map writes # 对map的并发写操作

因为它没有内置的锁机制来保护多个 goroutine 同时对其进行读写操作。当多个 goroutine 同时对同一个 map 进行读写操作时,就会出现数据竞争和不一致的结果。

就像上例那样,当两个 goroutine 同时尝试更新同一个键值对时,最终的结果可能取决于哪个 goroutine 先完成了更新操作。这种不确定性可能会导致程序出现错误或崩溃。

Go 语言团队没有将 map 设计成并发安全的,是因为这样会增加程序的开销并降低性能。

如果 map 内置了锁机制,那么每次访问 map 时都需要进行加锁和解锁操作,这会增加程序的运行时间并降低性能。

此外,并不是所有的程序都需要在并发场景下使用 map,因此将锁机制内置到 map 中会对那些不需要并发安全的程序造成不必要的开销。

在实际使用过程中,开发人员可以根据程序的需求来选择是否需要保证 map 的并发安全性,从而在性能和安全性之间做出权衡。

那怎么实现对map的安全的并发操作呢?

有三种方法,读写锁分片加锁sync.Map

  1. 读写锁,

    type SafeMap struct {
    	sync.RWMutex
    	Map map[string]string
    }
    
    func NewSafeMap() *SafeMap {
    	sm := new(SafeMap)
    	sm.Map = make(map[string]string)
    	return sm
    }
    
    func (sm *SafeMap) ReadMap(key string) string {
    	sm.RLock()
    	value := sm.Map[key]
    	sm.RUnlock()
    	return value
    }
    
    func (sm *SafeMap) WriteMap(key string, value string) {
    	sm.Lock()
    	sm.Map[key] = value
    	sm.Unlock()
    }
    
    func main() {
    	safeMap := NewSafeMap()
    
    	var wg sync.WaitGroup
    
    	// 启动多个goroutine进行写操作
    	for i := 0; i < 10; i++ {
    		wg.Add(1)
    		go func(i int) {
    			defer wg.Done()
    			safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i))
    		}(i)
    	}
    
    	// 启动多个goroutine进行读操作
    	for i := 0; i < 10; i++ {
    		wg.Add(1)
    		go func(i int) {
    			defer wg.Done()
    			fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i)))
    		}(i)
    	}
    
    	wg.Wait()
    
    	for key, value := range safeMap.Map {
    		fmt.Printf("Key: %s, Value: %s\n", key, value)
    	}
    }
    

    我们做了一个安全的map,在结构体内嵌入了sync.RWMutex,

    goroutine进行读操作的时候先加读锁,读锁不会阻塞其他goroutine,

    goroutine进行写操作的时候先加写锁,写锁会阻塞其他所有goroutine,

    这样我们就实现了一个安全的map。

  2. 分片加锁,我们仿照java中的concurrentHashmap1.7之前的设计思路,对整个map的分片加锁,比如,一个map有十个槽位,我们对前五个和后五个槽位分别加锁,前后五个槽位的访问互不影响,这样就实现了安全的并发。

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    // N 我们将整个map分成16个分片
    const N = 16
    
    type SafeMap struct {
    	//每个分片实际上就是一个小Map
    	maps  [N]map[string]string
    	locks [N]sync.RWMutex
    }
    
    func NewSafeMap() *SafeMap {
    	sm := new(SafeMap)
    	for i := 0; i < N; i++ {
    		sm.maps[i] = make(map[string]string)
    	}
    	return sm
    }
    
    func (sm *SafeMap) ReadMap(key string) string {
    	//只对这个分片加锁
    	index := hash(key) % N
    	sm.locks[index].RLock()
    	value := sm.maps[index][key]
    	sm.locks[index].RUnlock()
    	return value
    }
    
    func (sm *SafeMap) WriteMap(key string, value string) {
    	index := hash(key) % N
    	sm.locks[index].Lock()
    	sm.maps[index][key] = value
    	sm.locks[index].Unlock()
    }
    
    func hash(s string) int {
    	h := 0
    	for i := 0; i < len(s); i++ {
    		h = 31*h + int(s[i])
    	}
    	return h
    }
    
    func main() {
    	safeMap := NewSafeMap()
    
    	var wg sync.WaitGroup
    
    	// 启动多个goroutine进行写操作
    	for i := 0; i < 10; i++ {
    		wg.Add(1)
    		go func(i int) {
    			defer wg.Done()
    			safeMap.WriteMap(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i))
    		}(i)
    	}
    
    	// 启动多个goroutine进行读操作
    	for i := 0; i < 10; i++ {
    		wg.Add(1)
    		go func(i int) {
    			defer wg.Done()
    			fmt.Println(safeMap.ReadMap(fmt.Sprintf("name%d", i)))
    		}(i)
    	}
    
    	wg.Wait()
    }
    
  3. 除了上面的两个方法之外,还可以使用go语言官方的map,sync.map

    func main() {
        var m sync.Map
        var wg sync.WaitGroup
    
        // 启动多个goroutine进行写操作
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func(i int) {
                defer wg.Done()
                // 写操作,store
                m.Store(fmt.Sprintf("name%d", i), fmt.Sprintf("John%d", i))
            }(i)
        }
    
        // 启动多个goroutine进行读操作
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func(i int) {
                defer wg.Done()
                // 读操作load
                v, _ := m.Load(fmt.Sprintf("name%d", i))
                fmt.Println(v.(string))
            }(i)
        }
    
        wg.Wait()
    }
    

标签:wg,map,func,goroutine,并发,sm,Go,相关,string
From: https://www.cnblogs.com/DCFV/p/18255037

相关文章

  • go gin web服务器使用fvbock/endless优雅地重启或停止
    gin使用fvbock/endlessgin正常使用注册路由时:packagemainimport"github.com/gin-gonic/gin"funcmain(){ r:=gin.Default() r.GET("/ping",func(c*gin.Context){ c.JSON(200,gin.H{ "message":"pong", }) }) r.Run......
  • 一文理清GO语言日志库实现开发项目中的日志功能(rotatelogs/zap分析)
    一文理清GO语言日志库实现开发项目中的日志功能(rotatelogs/zap分析)rotatelogsrotatelogs是一个用于管理日志文件的Go语言库,它提供了自动轮换、压缩和删除旧日志文件的功能。这个库可以帮助你更好地管理和维护你的应用程序日志。要使用rotatelogs,你需要先安装它:goget......
  • Django框架入门必会三板斧,Django静态文件 static,Django有两种静态文件, Django框架
    ⅠDjango框架入门必会三板斧【一】Django项目如何添加新功能启动django项目之后如何添加更多的功能回想自己编写的web框架如果要添加功能就去urls.py和views.py【1】添加URL映射在项目的urls.py文件中,通过导入相应的应用(app)及其视图函数,并使用path()或include()......
  • 随着技术的不断发展,Perl 在处理大规模数据和高并发场景下的性能优化策略有哪些?
    Perl在处理大规模数据和高并发场景下的性能优化策略有以下几点:选择合适的数据结构:对于大规模数据,选择合适的数据结构可以提高Perl程序的性能。例如,使用数组而不是哈希表可以节省内存和加快访问速度。使用内置函数和操作符:Perl提供了许多内置函数和操作符,它们通常比自定......
  • 2024年,计算机相关专业还值得选择吗?——一入软件深似海,回首已是秃顶人
    2024年,计算机相关专业还值得选择吗?站在2024年的时间点上,计算机相关专业依然保持着其“万金油”特质,尽管行业内部的竞争确实日益激烈,但其长远发展潜力和就业前景仍然广阔。以下是几个方面的分析:作为今年的高考生判断是否适合计算机相关专业:兴趣与热情:首先,要审视自己是否......
  • Golang与设计模式
    单例模式因为之前研究Java比较多,所以当我试着使用go来实现一些设计模式的时候,首先想到的就是照搬Java中的思路,后面对go了解加深之后又增加了一些新的思路。在Java中实现的单例模式的思路有很多,但是比较好的两个思路是利用类加载机制生成单例对象,check-lock-check机制避免并发问......
  • 细读 Git | 让你弄懂 origin、HEAD、FETCH_HEAD 相关内容
    细读Git|让你弄懂origin、HEAD、FETCH_HEAD相关内容:https://blog.csdn.net/jsxztshaohaibo/article/details/124444377?spm=1001.2101.3001.6650.7&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-7-124444377-blog-137666223......
  • 查看mongo的bson数据文件
    转载请注明出处:BSON(BinarySerializedDocumentFormat)是MongoDB中用于存储和传输数据的一种二进制形式的存储格式,它基于JSON格式,但比JSON更松散,支持更多的数据类型。MongoDB使用BSON作为其文档的存储格式,这意味着当驱动程序使用文档进行插入、查询或其他操作时,会先将文档编......
  • go使用opentelemetry+jaeger---grpc
    clientpackagemainimport( "context" "fmt" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.op......
  • go使用opentelemetry+jaeger---gin+gprc
    api-1packagemainimport( "context" "fmt" "github.com/gin-gonic/gin" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "go.opentelemetry.io/otel" "go.opentelemetr......