【多线程笔记】信号量-Semaphore
互斥锁
一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex)。实现了互斥锁对象有:Monitor,Mutex,SpinLock
信号量
还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
Semaphore
Semaphore 继承自WaitHandle(Mutex也继承自WaitHandle),它用于锁机制,与Mutex不同的是,它允许指定数量的线程同时访问资源,在线程超过数量以后,则进行排队等待,直到之前的线程退出。Semaphore很适合应用于Web服务器这样的高并发场景,可以限制对资源访问的线程数。通常将Sempaphore声明为静态的。
SemaphoreSlim
Semaphore的WaitOne或者Release方法的调用大约会耗费1微秒的系统时间,而优化后的SemaphoreSlim则需要大致四分之一微秒。在计算中大量频繁使用它的时候SemaphoreSlim还是优势明显,加上SemaphoreSlim还丰富了不少接口,更加方便我们进行控制,所以在4.0以后的多线程开发中,推荐使用SemaphoreSlim。
Semaphore案例:
下面的示例代码演示了4条线程想要同时执行ThreadEntry()方法,但同时只允许2条线程进入:
class Program
{
static Semaphore sem = new Semaphore(2, 2);
const int threadSize = 4;
static void Main(string[] args)
{
for (int i = 0; i < threadSize; i++)
{
Thread thread = new Thread(ThreadEntry);
thread.Start(i + 1);
}
Console.ReadKey();
}
static void ThreadEntry(object id)
{
Console.WriteLine("线程{0}申请进入本方法", id);
// WaitOne:如果还有“空位”,则占位,如果没有空位,则等待;
sem.WaitOne();
Console.WriteLine("线程{0}成功进入本方法", id);
// 模拟线程执行了一些操作
Thread.Sleep(100);
Console.WriteLine("线程{0}执行完毕离开了", id);
// Release:释放一个“空位”
sem.Release();
}
}
解决缓存击穿案例
当多个线程同时请求同一个项目时,请求不会等待第一个完成。该项目将被创建多次。首次请求缓存,或者缓存失效会出现
例如,假设我们正在缓存头像,从数据库中获取头像需要 10 秒。如果我们在第一次请求后 2 秒请求头像,它将检查头像是否已缓存(尚未缓存),并开始另一次访问数据库。
public class WaitToFinishMemoryCache<TItem>
{
private MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
private ConcurrentDictionary<object, SemaphoreSlim> _locks = new ConcurrentDictionary<object, SemaphoreSlim>();
public async Task<TItem> GetOrCreate(object key, Func<Task<TItem>> createItem)
{
TItem cacheEntry;
if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.
{
SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));
await mylock.WaitAsync();
try
{
if (!_cache.TryGetValue(key, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = await createItem();
_cache.Set(key, cacheEntry);
}
}
finally
{
mylock.Release();
}
}
return cacheEntry;
}
}
用法:
var _avatarCache = new WaitToFinishMemoryCache<byte[]>();
// ...
var myAvatar = await _avatarCache.GetOrCreate(userId, async () => await _database.GetAvatar(userId));
也可是使用lock实现