SpinLock:
提供一个相互排斥锁基元,在该基元中,尝试获取锁的线程将在重复检查的循环中等待,直至该锁变为可用为止。
如果基于对象的锁定对象(Monitor)的系统开销由于垃圾回收过高,就可以使用SpinLock结构。SpinLock结构是在.NET4 开始引入的。如果有大量的锁定(例如:列表中的每一个节点都有一个锁),且锁定的时间非常短。SpinLock结构就很有用。
应避免使用多个 SpinLock 结构,也不要调用任何可能阻塞的内容。
除了体系结构上的区别之外,SpinLock结构的用法非常类似于 Monitor 类。获得锁定使用 Enter()或TryEnter()方法,释放锁定使用Exit()方法。SpinLock 结构还提供了属性 IsHeld 和 IsHeldByCurrentThread,知识它当前是否锁定。
✳注意:传送 SpinLock 要小心。因为 SpinLock 定义为结构,把一个变量赋予另一个变量会创建一个副本。总是通过引用传递SpinLock实例。
属性 | 描述 |
---|---|
IsHeld | 获取锁当前是否已由任何线程占用。 |
IsHeldByCurrentThread | 获取锁是否已由当前线程占用。 |
IsThreadOwnerTrackingEnabled | 获取是否已为此实例启用了线程所有权跟踪。 |
方法 | 描述 |
---|---|
Enter(Boolean) | 采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。 |
Exit() | 释放锁。 |
Exit(Boolean) | 释放锁。 |
TryEnter(Boolean) | 尝试采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁 |
TryEnter(Int32, Boolean) | 尝试采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。 |
TryEnter(TimeSpan, Boolean) | 尝试采用可靠的方式获取锁,这样,即使在方法调用中发生异常的情况下,都能采用可靠的方式检查 lockTaken 以确定是否已获取锁。 |
示例:
class Program
{
static void Main(string[] args)
{
//SpinLockSample1();
//SpinLockSample2();
SpinLockSample3();
Console.ReadKey();
}
/// <summary>
/// SpinLock 默认的方法,锁定和解锁
/// </summary>
static void SpinLockSample1()
{
SpinLock sl = new SpinLock();
StringBuilder sb = new StringBuilder();
// 每个并行作业所采取的操作。
//向StringBuilder追加10000次,使用自旋锁保护对sb的访问。
Action action = () =>
{
bool gotLock = false;
for (int i = 0; i < 10000; i++)
{
gotLock = false;
try
{
sl.Enter(ref gotLock);
sb.Append((i % 10).ToString());
}
finally
{
// 只有当真的得到了那把锁,你才会放弃它
if (gotLock) sl.Exit();
}
}
};
// 调用上述操作的3个并发实例
Parallel.Invoke(action, action, action);
// 检查/显示结果
Console.WriteLine("sb.Length = {0} (should be 30000)", sb.Length);
Console.WriteLine("number of occurrences of '5' in sb: {0} (should be 3000)",
sb.ToString().Where(c => (c == '0')).Count());
}
// Demonstrates:
// 默认自旋锁构造函数(跟踪线程所有者)
// SpinLock.Enter(ref bool)
// SpinLock.Exit() throwing exception
// SpinLock.IsHeld
// SpinLock.IsHeldByCurrentThread
// SpinLock.IsThreadOwnerTrackingEnabled
static void SpinLockSample2()
{
// 实例化一个自旋锁
SpinLock sl = new SpinLock();
// 这些 手动信号量 帮助排序下面的两个作业
ManualResetEventSlim mre1 = new ManualResetEventSlim(false);
ManualResetEventSlim mre2 = new ManualResetEventSlim(false);
bool lockTaken = false;
Task taskA = Task.Factory.StartNew(() =>
{
try
{
sl.Enter(ref lockTaken);
Console.WriteLine("Task A: 进入自旋锁");
mre1.Set(); // 信号任务B开始它的逻辑
//等待任务B完成其逻辑
/*
* (通常,您不会希望潜在地执行这样的操作
重量级的操作,同时持有自旋锁,但我们做到了
这里可以更有效地显示自旋锁属性
taskB)。
*/
mre2.Wait();
}
finally
{
if (lockTaken) sl.Exit();
}
});
Task taskB = Task.Factory.StartNew(() =>
{
mre1.Wait(); // 等待任务A给我发信号
Console.WriteLine("Task B: sl.IsHeld = {0} (should be true)", sl.IsHeld);
Console.WriteLine("Task B: sl.IsHeldByCurrentThread = {0} (should be false)", sl.IsHeldByCurrentThread);
Console.WriteLine("Task B: sl.IsThreadOwnerTrackingEnabled = {0} (should be true)", sl.IsThreadOwnerTrackingEnabled);
try
{
sl.Exit();
Console.WriteLine("Task B: 释放 sl, should not have been able to!");
}
catch (Exception e)
{
Console.WriteLine("Task B: sl.Exit resulted in exception, as expected: {0}", e.Message);
}
mre2.Set(); // 向任务A发送退出自旋锁的信号
});
// 等待任务完成并清理
Task.WaitAll(taskA, taskB);
mre1.Dispose();
mre2.Dispose();
}
// Demonstrates:
// 自旋锁构造函数(false)——未跟踪线程所有权
static void SpinLockSample3()
{
//创建不跟踪所有权/ threadid的自旋锁
SpinLock sl = new SpinLock(false);
// 用于与下面的任务同步 手动信号量
ManualResetEventSlim mres = new ManualResetEventSlim(false);
// 将验证下面的任务在单独的线程上运行
Console.WriteLine("主线程id = {0}", Thread.CurrentThread.ManagedThreadId);
/*
* 现在进入自旋锁。通常,不会想花那么多时间来持有自旋锁,但是我们在这里这样做的目的是演示一个非所有权跟踪自旋锁可以由一个不同于用于进入它的线程退出。
*/
bool lockTaken = false;
sl.Enter(ref lockTaken);
// 创建一个单独的任务,从中退出自旋锁
Task worker = Task.Factory.StartNew(() =>
{
Console.WriteLine("工作线程id = {0} (should be different than main thread id)",
Thread.CurrentThread.ManagedThreadId);
// 现在退出自旋锁
try
{
sl.Exit();
Console.WriteLine("worker任务:如预期一样成功退出自旋锁");
}
catch (Exception e)
{
Console.WriteLine("worker任务:退出自旋锁时发生意外故障: {0}", e.Message);
}
// 通知主线程继续
mres.Set();
});
/*
* wait()代替work . wait(),因为work . wait()可以内联worker任务,使其运行在同一个线程上。本例的目的是显示另一个线程可以退出在线程上创建的自旋锁(不跟踪线程)。
*/
mres.Wait();
// now Wait() on worker and clean up
worker.Wait();
mres.Dispose();
}
}
关于如何使用 SpinLock 进行低级别同步 参见官方文档: SpinLock 进行低级别同步
SpinLock仅应在确定这样做后使用才能改善应用程序的性能。 出于性能方面的考虑, 还SpinLock必须注意, 是值类型。 出于此原因, 必须注意不要意外复制SpinLock实例, 因为两个实例 (原始和副本) 将完全独立, 这可能会导致应用程序出现错误的行为。 如果必须传递实例, 则它应按引用而不是按值传递。
不要在只读SpinLock字段中存储实例。