- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
在使用 Monitor.PulseAll() 进行线程同步的库中,我注意到从调用 PulseAll(...) 到线程被唤醒的延迟似乎遵循“阶梯”分布 - - 步幅极大。被唤醒的线程几乎没有做任何工作;并几乎立即返回等待监视器。例如,在一个有 12 个内核和 24 个线程等待监视器的盒子上(2x Xeon5680/Gulftown;每个处理器 6 个物理内核;禁用 HT),脉冲和线程唤醒之间的延迟是这样的:
前 12 个线程(注意我们有 12 个内核)需要 30 到 60 微秒来响应。然后我们开始有很大的跳跃;稳定期在 700、1300、1900 和 2600 微秒左右。
我能够使用下面的代码独立于第 3 方库成功地重新创建此行为。这段代码所做的是启动大量线程(更改 numThreads 参数),这些线程只是在监视器上等待,读取时间戳,将其记录到 ConcurrentSet,然后立即返回等待。每秒钟 PulseAll() 唤醒所有线程。它执行 20 次,并向控制台报告第 10 次迭代的延迟。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace PulseAllTest
{
class Program
{
static long LastTimestamp;
static long Iteration;
static object SyncObj = new object();
static Stopwatch s = new Stopwatch();
static ConcurrentBag<Tuple<long, long>> IterationToTicks = new ConcurrentBag<Tuple<long, long>>();
static void Main(string[] args)
{
long numThreads = 32;
for (int i = 0; i < numThreads; ++i)
{
Task.Factory.StartNew(ReadLastTimestampAndPublish, TaskCreationOptions.LongRunning);
}
s.Start();
for (int i = 0; i < 20; ++i)
{
lock (SyncObj)
{
++Iteration;
LastTimestamp = s.Elapsed.Ticks;
Monitor.PulseAll(SyncObj);
}
Thread.Sleep(TimeSpan.FromSeconds(1));
}
Console.WriteLine(String.Join("\n",
from n in IterationToTicks where n.Item1 == 10 orderby n.Item2
select ((decimal)n.Item2)/TimeSpan.TicksPerMillisecond));
Console.Read();
}
static void ReadLastTimestampAndPublish()
{
while(true)
{
lock(SyncObj)
{
Monitor.Wait(SyncObj);
}
IterationToTicks.Add(Tuple.Create(Iteration, s.Elapsed.Ticks - LastTimestamp));
}
}
}
}
使用上面的代码,这是一个启用了 8 个内核/w 超线程(即任务管理器中的 16 个内核)和 32 个线程(*2x Xeon5550/Gainestown;每个处理器 4 个物理内核;启用 HT)的机器上的延迟示例):
编辑:为了尝试将 NUMA 排除在等式之外,下面是在 Core i7-3770(Ivy Bridge)上运行具有 16 个线程的示例程序的图表; 4 个物理内核;超线程启用:
谁能解释为什么 Monitor.PulseAll() 会这样?
编辑2:
为了尝试证明这种行为并不是同时唤醒一堆线程所固有的,我使用事件复制了测试程序的行为;我没有测量 PulseAll() 的延迟,而是测量了 ManualResetEvent.Set() 的延迟。该代码正在创建多个工作线程,然后等待同一 ManualResetEvent 对象上的 ManualResetEvent.Set() 事件。当事件被触发时,他们会进行延迟测量,然后立即等待他们自己的每线程 AutoResetEvent。在下一次迭代之前(500 毫秒之前),ManualResetEvent 为 Reset(),然后每个 AutoResetEvent 为 Set(),因此线程可以返回等待共享的 ManualResetEvent。
我对发布这个犹豫不决,因为它可能是一个巨大的红色听证会(我没有声称事件和监视器的行为相似)加上它使用一些绝对糟糕的做法让事件表现得像监视器(我喜欢/讨厌如果我将其提交给代码审查,看看我的同事会怎么做);但我认为结果很有启发性。
本次测试与原测试在同一台机器上完成;一个 2xXeon5680/Gulftown;每个处理器 6 个内核(总共 12 个内核);禁用超线程。
如果不是很明显这与 Monitor.PulseAll 有多么不同;这是叠加在最后一张图上的第一张图:
用于生成这些测量值的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace MRETest
{
class Program
{
static long LastTimestamp;
static long Iteration;
static ManualResetEventSlim MRES = new ManualResetEventSlim(false);
static List<ReadLastTimestampAndPublish> Publishers =
new List<ReadLastTimestampAndPublish>();
static Stopwatch s = new Stopwatch();
static ConcurrentBag<Tuple<long, long>> IterationToTicks =
new ConcurrentBag<Tuple<long, long>>();
static void Main(string[] args)
{
long numThreads = 24;
s.Start();
for (int i = 0; i < numThreads; ++i)
{
AutoResetEvent ares = new AutoResetEvent(false);
ReadLastTimestampAndPublish spinner = new ReadLastTimestampAndPublish(
new AutoResetEvent(false));
Task.Factory.StartNew(spinner.Spin, TaskCreationOptions.LongRunning);
Publishers.Add(spinner);
}
for (int i = 0; i < 20; ++i)
{
++Iteration;
LastTimestamp = s.Elapsed.Ticks;
MRES.Set();
Thread.Sleep(500);
MRES.Reset();
foreach (ReadLastTimestampAndPublish publisher in Publishers)
{
publisher.ARES.Set();
}
Thread.Sleep(500);
}
Console.WriteLine(String.Join("\n",
from n in IterationToTicks where n.Item1 == 10 orderby n.Item2
select ((decimal)n.Item2) / TimeSpan.TicksPerMillisecond));
Console.Read();
}
class ReadLastTimestampAndPublish
{
public AutoResetEvent ARES { get; private set; }
public ReadLastTimestampAndPublish(AutoResetEvent ares)
{
this.ARES = ares;
}
public void Spin()
{
while (true)
{
MRES.Wait();
IterationToTicks.Add(Tuple.Create(Iteration, s.Elapsed.Ticks - LastTimestamp));
ARES.WaitOne();
}
}
}
}
}
最佳答案
这些版本之间的一个区别是,在 PulseAll 的情况下 - 线程立即重复循环,再次锁定对象。
你有12个核心,所以有12个线程在运行,执行循环,再次进入循环,锁定对象(一个接一个),然后进入等待状态。其他线程一直在等待。在 ManualEvent 情况下,您有两个事件,因此线程不会立即重复循环,而是在 ARES 事件上被阻塞 - 这允许其他线程更快地获取锁所有权。
我通过在 ReadLastTimestampAndPublish 的循环末尾添加 sleep 来模拟 PulseAll 中的类似行为。这让其他线程可以更快地锁定 syncObj,并且似乎可以提高我从程序中获得的数字。
static void ReadLastTimestampAndPublish()
{
while(true)
{
lock(SyncObj)
{
Monitor.Wait(SyncObj);
}
IterationToTicks.Add(Tuple.Create(Iteration, s.Elapsed.Ticks - LastTimestamp));
Thread.Sleep(TimeSpan.FromMilliseconds(100)); // <===
}
}
关于c# - 为什么 Monitor.PulseAll 在信号线程中导致 "stepping stair"延迟模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20860709/
我正在为我们正在运行的 ASP.NET 应用程序编写我们自己的记录器。在当前的代码库中,我们似乎遇到了线程问题,我正在尝试逐个组件地消除以找出导致此问题的原因。症状是应用程序启动后网络服务器上的 CP
任何人都可以用简单的例子来解释我如何处理 Monitor.PulseAll()。我已经从这个 stackoverflow 中找到了一些例子。因为我是初学者,我觉得这些超出了我的理解范围。 最佳答案 怎
我有一种情况,有时休眠线程不会被 Monitor.PulseAll(object lock) 命令唤醒。这种现象不是确定性的。通常它可以工作,但有时在调试期间,休眠线程无法唤醒并且我的队列不断填满
我很难理解 Wait()、Pulse()、PulseAll()。他们都能避免僵局吗?如果您解释一下如何使用它们,我将不胜感激? 最佳答案 简短版: lock(obj) {...} 是 Monitor.
Delphi Docwiki 解释说 Pulse通知等待队列中的下一个线程,一旦调用线程释放该对象,它将能够锁定指定的对象。 PulseAll向等待队列中的所有线程发出信号。 我found这段代码在线
在有多个“请求线程”和一个哑“工作线程”的情况下,请求线程必须排队。 考虑两种可能性: 每个请求线程在其自己的专用 对象上调用 Monitor.Wait,该对象进入 FIFO 队列。当结果到达时,最旧
我是 CSharp 和线程的新手。 为了熟悉Monitor.Wait、Monitor.lock和Monitor.PulseAll,我构建了一个场景描述如下。 “一个 FootballGround 由不
Monitor.PulseAll 通知队列中的所有等待线程。 Monitor.Pulse 通知等待队列中的一个线程。 (下一个等待线程) 只有下一个线程(一个线程)才能获取锁。那有什么区别呢? 什么时
是否有 .Net 类可以执行 ManualResetEvent.PulseAll() 会执行的操作(如果存在)? 我需要自动释放一组等待同一信号的线程。 (对于我的预期用途,我并不担心“线程踩踏”。)
在使用 Monitor.PulseAll() 进行线程同步的库中,我注意到从调用 PulseAll(...) 到线程被唤醒的延迟似乎遵循“阶梯”分布 - - 步幅极大。被唤醒的线程几乎没有做任何工作;
我是一名优秀的程序员,十分优秀!