惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

阮一峰的网络日志
阮一峰的网络日志
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
The Last Watchdog
The Last Watchdog
Cyberwarzone
Cyberwarzone
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cyber Attacks, Cyber Crime and Cyber Security
L
Lohrmann on Cybersecurity
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
The Cloudflare Blog
V
V2EX
博客园_首页
博客园 - 聂微东
Vercel News
Vercel News
人人都是产品经理
人人都是产品经理
G
GRAHAM CLULEY
T
Tenable Blog
Last Week in AI
Last Week in AI
Y
Y Combinator Blog
L
LINUX DO - 最新话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
SecWiki News
SecWiki News
博客园 - 三生石上(FineUI控件)
S
Secure Thoughts
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
T
Troy Hunt's Blog
博客园 - 【当耐特】
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
A
About on SuperTechFans
B
Blog RSS Feed
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
罗磊的独立博客
D
DataBreaches.Net
P
Privacy & Cybersecurity Law Blog
Schneier on Security
Schneier on Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Jina AI
Jina AI
D
Docker
P
Proofpoint News Feed

博客园 - Zhenway

Azure SignalR支持replication啦 Azure SignalR总览 定制json序列化 今天折腾这么一个正则 docfx组件介绍--YamlSerialization docfx daylybuild docfx开源啦 docfx组件介绍--MarkdownLite docfx预热中 合并批量请求 一个简单的Linq to TreeNode 在finally中调用一个需要await的方法 当泛型方法推断,扩展方法遇到泛型类型in/out时。。。 4.5你太黑了,不带这么玩TypeForwardedTo的 一个非常简单的反射加速方案 又发现一个msdn的坑 又发现个.net framework的坑 sql server死锁神器 踩到一个Emit的坑,留个纪念
加载时预防并发执行
Zhenway · 2014-01-14 · via 博客园 - Zhenway

最近代码里面写了一个缓存,有了一个简单的想法:

通常我们会有一个Cache(例如:MemoryCache)去缓存一些对象,那么当这个缓存项过期时,可能同时有很多线程都需要这个缓存项,那么就会有并发的去加载的情况发生,当然,如果这个加载时间并不长的话,那也没什么问题,但是如果加载过程本身比较慢,又比较消耗资源的话,恐怕就比较杯具了。

那么如果能让Cache不命中时,加载过程能串行,那么也有个问题,不通的缓存项在加载过程中其实不需要串行,这样整体的效率又会下降。

于是,我思考了一种既能每个资源串行加载,又能保证不同资源并行加载的方式。

简单的看就是:

这里用到了一个loader task,问题是这个loader task也需要线程安全,这可也不是一件轻松的事情,好在我们有TPL,Task<T>类除了Dispose方法在其它成员都是线程安全的,这自然也就包括了Result属性。

然后可能有人会有些疑问:

第一个问题,会不会用另一个线程去加载

答案是不确定,在线程池空闲的情况下,确实会用其他线程去加载。

第二个问题,在线程池忙碌的时候,会不会等其他任务,而导致更慢,或者严重的导致死锁

答案是不会,甚至说,这里用task,本质上是期待线程池处于一个忙碌的状态,这样可以防止加载过程跑到其他线程上去

至于什么,我们可以看下面这段代码:

 1             ThreadPool.SetMinThreads(4, 100);
 2             ThreadPool.SetMaxThreads(4, 100);
 3             var dict = new Dictionary<string, Task<int>>();
 4             for (int i = 0; i < 8; i++)
 5             {
 6                 ThreadPool.QueueUserWorkItem(_ =>
 7                 {
 8                     Console.WriteLine("Thread:{0}, starting...", Thread.CurrentThread.ManagedThreadId);
 9                     Thread.Sleep(500);
10                     bool ownTask = false;
11                     Task<int> task;
12                     try
13                     {
14                         lock (dict)
15                         {
16                             if (!dict.TryGetValue("foo", out task))
17                             {
18                                 ownTask = true;
19                                 task = Task.Factory.StartNew(() =>
20                                 {
21                                     Thread.Sleep(100);
22                                     return Thread.CurrentThread.ManagedThreadId;
23                                 });
24                                 dict["foo"] = task;
25                             }
26                         }
27                         Console.WriteLine("Thread:{0}, own task:{1}, waiting result...", Thread.CurrentThread.ManagedThreadId, ownTask);
28                         Console.WriteLine("Thread:{0}, own task:{1}, result:{2}", Thread.CurrentThread.ManagedThreadId, ownTask, task.Result);
29                     }
30                     finally
31                     {
32                         // add to cache
33                         if (ownTask)
34                         {
35                             lock (dict)
36                             {
37                                 dict.Remove("foo");
38                             }
39                         }
40                     }
41                 });
42             }
43             Thread.Sleep(2000);
44             lock (dict)
45             {
46                 Console.WriteLine("dict count:{0}", dict.Count);
47             }

View Code

我们会发现即使线程池的线程全部被占满的情况下,这段代码也不会卡住,相反,会使用ownTask为true的那个线程来同步执行loader task,这是依赖于TPL中的任务窃取功能。

简单的说,这个功能就是:如果TPL发现需要等待一个任务的执行完成,并且这个任务并没有开始执行时,把这个任务从任务队列中窃取过来,同步执行。

到这里,本篇随笔也已经完成了。