Locking by string. Is this safe/sane?

前端 未结 9 1630
-上瘾入骨i
-上瘾入骨i 2020-12-14 01:33

I need to lock a section of code by string. Of course the following code is hideously unsafe:

lock(\"http://someurl\")
{
    //bla
}

So I\'

相关标签:
9条回答
  • 2020-12-14 01:34

    Another option is to get the HashCode of each URL, then divide it by a prime number and use it as an index into an array of locks. This will limit the number of locks you need while letting you control the probability of a “false locking” by choose the number of locks to use.

    However the above is only worthwhile if it is too costly just have one lock per active url.

    0 讨论(0)
  • 2020-12-14 01:36

    You've created a pretty complex wrapper around a simple lock statement. Wouldn't it be better to create a dictionary of url's and create a lock object for each and everyone. You could simply do.

    objLink = GetUrl("Url"); //Returns static reference to url/lock combination
    lock(objLink.LockObject){
        //Code goes here
    }
    

    You could even simplify this by locking the objLink object directly wich could be the GetUrl("Url") string instance. (you'd have to lock the static list of strings though)

    You're original code if very error prone. If the code:

    if (obj.Return())
    {
    keyLocks.Remove(url);
    }
    

    If the original finally code throws an exception you're left with an invalid LockObject state.

    0 讨论(0)
  • 2020-12-14 01:37

    I added a solution in Bardock.Utils package inspired by @eugene-beresovsky answer.

    Usage:

    private static LockeableObjectFactory<string> _lockeableStringFactory = 
        new LockeableObjectFactory<string>();
    
    string key = ...;
    
    lock (_lockeableStringFactory.Get(key))
    {
        ...
    }
    

    Solution code:

    namespace Bardock.Utils.Sync
    {
        /// <summary>
        /// Creates objects based on instances of TSeed that can be used to acquire an exclusive lock.
        /// Instanciate one factory for every use case you might have.
        /// Inspired by Eugene Beresovsky's solution: https://stackoverflow.com/a/19375402
        /// </summary>
        /// <typeparam name="TSeed">Type of the object you want lock on</typeparam>
        public class LockeableObjectFactory<TSeed>
        {
            private readonly ConcurrentDictionary<TSeed, object> _lockeableObjects = new ConcurrentDictionary<TSeed, object>();
    
            /// <summary>
            /// Creates or uses an existing object instance by specified seed
            /// </summary>
            /// <param name="seed">
            /// The object used to generate a new lockeable object.
            /// The default EqualityComparer<TSeed> is used to determine if two seeds are equal. 
            /// The same object instance is returned for equal seeds, otherwise a new object is created.
            /// </param>
            public object Get(TSeed seed)
            {
                return _lockeableObjects.GetOrAdd(seed, valueFactory: x => new object());
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-14 01:41

    Now I have the same issue. I decided to use simple solution: to avoid deadlocks create a new string with fixed unique prefix and use it as a lock object:

    lock(string.Intern("uniqueprefix" + url))
    {
    //
    }
    
    0 讨论(0)
  • 2020-12-14 01:46

    About 2 years ago i had to implement the same thing:

    I'm trying to write an image cache where multiple clients (over a 100 or so) are likely to request the image simultaneously. The first hit will request the image from another server, and I'd like all other requests to the cache to block until this operation is complete so that they can retrieve the cached version.

    I ended up with code doing pretty the same as yours (the dictionary of LockObjects). Well, yours seems to be better encapsulated.

    So, I think you have quite a good solution. Just 2 comments:

    1. If you need even better peformance it maybe useful to utilize some kind of ReadWriteLock, since you have 100 readers and only 1 writer getting the image from another server.

    2. I am not sure what happens in case of thread switch just before Monitor.Enter(obj); in your LockOperation(). Say, the first thread wanting the image constructs a new lock and then thread switch just before it enters critical section. Then it could happen that the second thread enters the critical section before the first. Well could be that this is not a real problem.

    0 讨论(0)
  • 2020-12-14 01:49

    Locking by an arbitrary string instance would be a bad idea, because Monitor.Lock locks the instance. If you had two different string instances with the same content, that would be two independent locks, which you don't want. So you're right to be concerned about arbitrary strings.

    However, .NET already has a built-in mechanism to return the "canonical instance" of a given string's content: String.Intern. If you pass it two different string instances with the same content, you will get back the same result instance both times.

    lock (string.Intern(url)) {
        ...
    }
    

    This is simpler; there's less code for you to test, because you'd be relying on what's already in the Framework (which, presumably, already works).

    0 讨论(0)
提交回复
热议问题