并发写入包含Golang中另一个Map的Map

并发写入包含Golang中另一个Map的Map

问题描述:

In Golang we have to sync concurrent changes to the Map. If my Map contains another Map like this:

map[string]map[string]*CustomStruct

.. do I have to use Lock in all of them when writing something?

If I'll write something into internal Map -> outer Map will also be changed, so I still have to sync outer Map's changes.

If I Lock outer Map's changes -> no one else can write into internal Map -> there is no point to Lock internal Map.

Am I right or it works in a different way and I must lock all Maps?

My understanding is that there aren't any hard and fast rules here.

For example, you could coordinate all map writes via a mutex that's not even stored on the map, there is no inherent link between any mutex and your map - its just about how you use them to coordinate access to your data.

Essentially if you lock individual elements you will see less contention over the locks because the locks will be acquired less often, if you use an outer lock over all maps there will be more contention as more processes will be trying to acquire the same lock which will reduce how much actual work your processes can get done.

It's ultimately up to you to work out what works best for your use case

Help with that:

There is a new tool available in go 1.9 that allows you to benchmark lock contention to see what approach is most efficient for your application.

Building and running your app with the -race flag will help determine if the locks are doing their job correctly

You could also take a look at the new sync maps which I haven't played with yet but I understand handle this for you:

Sync maps

The simple (and most likely good enough) solution is to have a mutex that protects the whole structure. It can be a regular mutex or a read-write mutex, in case reads are much more frequent than writes. This is easy to implement, easy to reason about and in the vast majority of cases it will do the job.

If on the other hand you're looking for a challenge, then you can take this into a number of directions:

  • Have a top-level read-write mutex and one (simple or read-write) mutex per top-level map key. Write lock the top-level mutex when adding or deleting entries to the top level map; else read lock it, acquire the entry-specific lock and read or write the entry (which is also a map).
  • Decide on what kind of parallelism you want and split your top-level map into shards, with keys sharded based on their hash and one mutex per shard. Then you can have parallel writes (key adds and removes) to multiple keys as long as they are in different shards.
  • Apply the second approach to the second-level maps too. Go crazy! :o)

But, in all seriousness, unless this map is huge, much of your application is simply adding and removing stuff from it, and you're willing to put a lot of effort into benchmarking and tweaking your implementation, go with the single lock approach. It is actually going to be much faster in the general case and, as a bonus, it's simple to implement.