I need to lock a section of code by string. Of course the following code is hideously unsafe:
lock(\"http://someurl\")
{
//bla
}
So I\'
This is how I implemented this locking schema:
public class KeyLocker<TKey>
{
private class KeyLock
{
public int Count;
}
private readonly Dictionary<TKey, KeyLock> keyLocks = new Dictionary<TKey, KeyLock>();
public T ExecuteSynchronized<T>(TKey key, Func<TKey, T> function)
{
KeyLock keyLock = null;
try
{
lock (keyLocks)
{
if (!keyLocks.TryGetValue(key, out keyLock))
{
keyLock = new KeyLock();
keyLocks.Add(key, keyLock);
}
keyLock.Count++;
}
lock (keyLock)
{
return function(key);
}
}
finally
{
lock (keyLocks)
{
if (keyLock != null && --keyLock.Count == 0) keyLocks.Remove(key);
}
}
}
public void ExecuteSynchronized(TKey key, Action<TKey> action)
{
this.ExecuteSynchronized<bool>(key, k =>
{
action(k);
return true;
});
}
}
And used like this:
private locker = new KeyLocker<string>();
......
void UseLocker()
{
locker.ExecuteSynchronized("some string", () => DoSomething());
}
Here is a very simple, elegant and correct solution for .NET 4 using ConcurrentDictionary
adapted from this question.
public static class StringLocker
{
private static readonly ConcurrentDictionary<string, object> _locks = new ConcurrentDictionary<string, object>();
public static void DoAction(string s, Action action)
{
lock(_locks.GetOrAdd(s, new object()))
{
action();
}
}
}
You can use this like so:
StringLocker.DoAction("http://someurl", () =>
{
...
});
In most cases, when you think you need locks, you don't. Instead, try to use thread-safe data structure (e.g. Queue) which handles the locking for you.
For example, in python:
class RequestManager(Thread):
def __init__(self):
super().__init__()
self.requests = Queue()
self.cache = dict()
def run(self):
while True:
url, q = self.requests.get()
if url not in self.cache:
self.cache[url] = urlopen(url).read()[:100]
q.put(self.cache[url])
# get() runs on MyThread's thread, not RequestManager's
def get(self, url):
q = Queue(1)
self.requests.put((url, q))
return q.get()
class MyThread(Thread):
def __init__(self):
super().__init__()
def run(self):
while True:
sleep(random())
url = ['http://www.google.com/', 'http://www.yahoo.com', 'http://www.microsoft.com'][randrange(0, 3)]
img = rm.get(url)
print(img)