【多线程笔记】信号量-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实现

参考:
https://www.cnblogs.com/edisonchou/p/4848131.html

posted @ 2020-06-14 22:39  .Neterr  阅读(374)  评论(0编辑  收藏  举报