如何实现 Go 并发 map 或 slice 以更快地管理使用中的资源?

图像您有一个结构,该结构表示一次只有一个用户可以访问的资源。可能看起来像这样:


type Resource struct{

    InUse bool//or int32/int64 is you want to use atomics

    Resource string //parameters that map to the resource, think `/dev/chardeviceXXX`

}

这些资源的数量是有限的,用户将随机同时请求访问它们,因此您将它们打包在管理器中


type ResourceManager struct{

    Resources []*Resource //or a map 

}

我正在尝试为经理找出最佳、安全的方法来创建一个功能,该功能func (m *ResourceManager)GetUnusedResouce()(*Resouce,error)将:

  • 遍历所有资源,直到找到不是 InUse 的资源

  • 将其标记为 InUse 并将 *Resouce 返回到调用上下文/goroutine

  • 我会锁定以避免任何系统级锁定(flock)并在 Go 中完成这一切

  • 还需要有一个功能来标记资源不再使用

现在,当我遍历整个切片时,我在管理器中使用互斥锁来锁定访问。这是安全的,但我希望能够通过同时搜索已使用的资源并处理两个尝试将同一资源标记为 InUse 的 goroutine 来加快速度。

更新

我特别想知道是否将 ResourceInUse字段设置为 anint64然后 usingatomic.CompareAndSwapInt64将允许资源管理器在找到未使用的资源时正确锁定:

func (m *ResourceManager)GetUnusedResouce()(*Resouce,error){

    for i := range Resources{

        if atomic.CompareAndSwapInt64(&Resouces[i].InUse,1){

            return Resouces[i],nil

        }

    }

    return nil, errors.New("all resouces in use")

}

任何可以更好地测试这一点的单元测试也将不胜感激。


慕虎7371278
浏览 111回答 2
2回答

梦里花落0921

问题中的GetUnusedResouce函数可能会对所有资源执行比较和交换操作。根据资源数量和应用程序访问模式,执行受互斥体保护的少量操作会更快。使用单链表实现快速获取和放置操作。type Resource struct {&nbsp; &nbsp; next&nbsp; &nbsp; &nbsp;*Resource&nbsp; &nbsp; Resource string}type ResourceManager struct {&nbsp; &nbsp; free *Resource&nbsp; &nbsp; mu&nbsp; &nbsp;sync.Mutex}// Get gets a free resource from the manager or returns// nil when the manager is empty.func (m *ResourceManager) Get() *Resource {&nbsp; &nbsp; m.mu.Lock()&nbsp; &nbsp; defer m.mu.Unlock()&nbsp; &nbsp; result := m.free&nbsp; &nbsp; if m.free != nil {&nbsp; &nbsp; &nbsp; &nbsp; m.free = m.free.next&nbsp; &nbsp; }&nbsp; &nbsp; return result}// Put returns a resource to the pool.func (m *ResourceManager) Put(r *Resource) {&nbsp; &nbsp; m.mu.Lock()&nbsp; &nbsp; defer m.mu.Unlock()&nbsp; &nbsp; r.next = m.free&nbsp; &nbsp; m.free = r}这是一个在测试中使用的示例:func TestResourceManager(t *testing.T) {&nbsp; &nbsp; // Add free resources to a manager.&nbsp; &nbsp; var m ResourceManager&nbsp; &nbsp; m.Put(&Resource{Resource: "/dev/a"})&nbsp; &nbsp; m.Put(&Resource{Resource: "/dev/b"})&nbsp; &nbsp; // Test that we can get all resources from the pool.&nbsp; &nbsp; ra := m.Get()&nbsp; &nbsp; rb := m.Get()&nbsp; &nbsp; if ra.Resource > rb.Resource {&nbsp; &nbsp; &nbsp; &nbsp; // Sort ra, rb to make test independent of order.&nbsp; &nbsp; &nbsp; &nbsp; ra, rb = rb, ra&nbsp; &nbsp; }&nbsp; &nbsp; if ra == nil || ra.Resource != "/dev/a" {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("ra is %v, want /dev/a", ra)&nbsp; &nbsp; }&nbsp; &nbsp; if rb == nil || rb.Resource != "/dev/b" {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("rb is %v, want /dev/b", rb)&nbsp; &nbsp; }&nbsp; &nbsp; // Check for empty pool.&nbsp; &nbsp; r := m.Get()&nbsp; &nbsp; if r != nil {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("r is %v, want nil", r)&nbsp; &nbsp; }&nbsp; &nbsp; // Return one resource and try again.&nbsp; &nbsp; m.Put(ra)&nbsp; &nbsp; ra = m.Get()&nbsp; &nbsp; if ra == nil || ra.Resource != "/dev/a" {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("ra is %v, want /dev/a", ra)&nbsp; &nbsp; }&nbsp; &nbsp; r = m.Get()&nbsp; &nbsp; if r != nil {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("r is %v, want nil", r)&nbsp; &nbsp; }}在操场上运行测试。如果资源数量存在已知的合理限制,请使用通道。这种方法利用了运行时高度优化的通道实现。type Resource struct {&nbsp; &nbsp; Resource string}type ResourceManager struct {&nbsp; &nbsp; free chan *Resource}// Get gets a free resource from the manager or returns// nil when the manager is empty.func (m *ResourceManager) Get() *Resource {&nbsp; &nbsp; select {&nbsp; &nbsp; case r := <-m.free:&nbsp; &nbsp; &nbsp; &nbsp; return r&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; }}// Put returns a resource to the pool.func (m *ResourceManager) Put(r *Resource) {&nbsp; &nbsp; m.free <- r}// NewResourceManager returns a manager that can hold up to// n free resources.func NewResourceManager(n int) *ResourceManager {&nbsp; &nbsp; return &ResourceManager{free: make(chan *Resource, n)}}使用上面的函数测试这个实现TestResourceManager,但是var m ResourceManager用m := NewResourceManager(4).在 Go 操场上运行测试。

千万里不及你

给定资源是否在使用中不是Resource自身的属性,而是ResourceManager.事实上,没有必要跟踪正在使用的资源(除非由于问题中未提及的某种原因您需要)。使用中的资源在释放时可以简单地放回池中。这是使用通道的可能实现。不需要一个互斥体,也不需要任何原子 CAS。package mainimport (&nbsp; &nbsp; fmt "fmt"&nbsp; &nbsp; "time")type Resource struct {&nbsp; &nbsp; Data string}type ResourceManager struct {&nbsp; &nbsp; resources []*Resource&nbsp; &nbsp; closeCh&nbsp; &nbsp;chan struct{}&nbsp; &nbsp; acquireCh chan *Resource&nbsp; &nbsp; releaseCh chan *Resource}func NewResourceManager() *ResourceManager {&nbsp; &nbsp; r := &ResourceManager{&nbsp; &nbsp; &nbsp; &nbsp; closeCh:&nbsp; &nbsp;make(chan struct{}),&nbsp; &nbsp; &nbsp; &nbsp; acquireCh: make(chan *Resource),&nbsp; &nbsp; &nbsp; &nbsp; releaseCh: make(chan *Resource),&nbsp; &nbsp; }&nbsp; &nbsp; go r.run()&nbsp; &nbsp; return r}func (r *ResourceManager) run() {&nbsp; &nbsp; defer close(r.acquireCh)&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; if len(r.resources) > 0 {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case r.acquireCh <- r.resources[len(r.resources)-1]:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r.resources = r.resources[:len(r.resources)-1]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case res := <-r.releaseCh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r.resources = append(r.resources, res)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-r.closeCh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case res := <-r.releaseCh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r.resources = append(r.resources, res)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-r.closeCh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}func (r *ResourceManager) AcquireResource() *Resource {&nbsp; &nbsp; return <-r.acquireCh}func (r *ResourceManager) ReleaseResource(res *Resource) {&nbsp; &nbsp; r.releaseCh <- res}func (r *ResourceManager) Close() {&nbsp; &nbsp; close(r.closeCh)}// small demo below ...func test(id int, r *ResourceManager) {&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; res := r.AcquireResource()&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("test %d: %s\n", id, res.Data)&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(time.Millisecond)&nbsp; &nbsp; &nbsp; &nbsp; r.ReleaseResource(res)&nbsp; &nbsp; }}func main() {&nbsp; &nbsp; r := NewResourceManager()&nbsp; &nbsp; r.ReleaseResource(&Resource{"Resource A"}) // initial setup&nbsp; &nbsp; r.ReleaseResource(&Resource{"Resource B"}) // initial setup&nbsp; &nbsp; go test(1, r)&nbsp; &nbsp; go test(2, r)&nbsp; &nbsp; go test(3, r) // 3 consumers, but only 2 resources ...&nbsp; &nbsp; time.Sleep(time.Second)&nbsp; &nbsp; r.Close()}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go