基本信息
如果持有锁的时间非常短,而且锁的粒度很精细,那么使用自旋锁会获得更好的性能。有时候,Monitor互斥锁的开销还是相当大的。但SpinLock 的与Monitor的使用形式还是基本类似的。
一段简单的代码示例:
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Sample5_4_spinlock
{
class Program
{
private static int _TaskNum = 3;
private static Task[] _Tasks;
private static StringBuilder _StrBlder;
private const int RUN_LOOP = 50;
private static SpinLock m_spinlock;
private static void Work1(int TaskID)
{
int i = 0;
string log = "";
bool lockToken = false;
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
lockToken = false;
m_spinlock.Enter(ref lockToken);
_StrBlder.Append(log);
}
finally
{
if (lockToken)
m_spinlock.Exit(false);
}
}
}
private static void Work2(int TaskID)
{
int i = 0;
string log = "";
bool lockToken = false;
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
lockToken = false;
m_spinlock.Enter(ref lockToken);
_StrBlder.Append(log);
}
finally
{
if (lockToken)
m_spinlock.Exit(false);
}
}
}
private static void Work3(int TaskID)
{
int i = 0;
string log = "";
bool lockToken = false;
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
lockToken = false;
m_spinlock.Enter(ref lockToken);
_StrBlder.Append(log);
}
finally
{
if (lockToken)
m_spinlock.Exit(false);
}
}
}
static void Main(string[] args)
{
_Tasks = new Task[_TaskNum];
_StrBlder = new StringBuilder();
m_spinlock = new SpinLock();
_Tasks[0] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work1(taskid);
}, 0);
_Tasks[1] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work2(taskid);
}, 1);
_Tasks[2] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work3(taskid);
}, 2);
var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
{
Task.WaitAll(_Tasks);
Console.WriteLine("==========================================================");
Console.WriteLine("All Phase is completed");
Console.WriteLine("==========================================================");
Console.WriteLine(_StrBlder);
});
try
{
finalTask.Wait();
}
catch (AggregateException aex)
{
Console.WriteLine("Task failed And Canceled" + aex.ToString());
}
finally
{
}
Console.ReadLine();
}
}
}
在每个任务的finally块中,会调用SpinLock的release,否则在SpinLock.Enter中,程序会在循环中不断的尝试获得锁,造成死锁。一旦获得了锁,ref 的 LockToken会被变成 true。
使用超时
SpinLock 同样也提供了超时机制供开发使用。
一个程序示例,worker 1会造成超时,work 2 和work 3则在程序中捕获超时产生的异常,终止运行。
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Sample5_5_spinlock_timeout
{
class Program
{
private static int _TaskNum = 3;
private static Task[] _Tasks;
private static StringBuilder _StrBlder;
private const int RUN_LOOP = 50;
private static SpinLock m_spinlock;
private static void Work1(int TaskID)
{
int i = 0;
string log = "";
bool lockToken = false;
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
lockToken = false;
m_spinlock.TryEnter(2000, ref lockToken);
if (!lockToken)
{
Console.WriteLine("Work1 TIMEOUT!! Will throw Exception");
throw new TimeoutException("Work1 TIMEOUT!!");
}
System.Threading.Thread.Sleep(5000);
_StrBlder.Append(log);
}
finally
{
if (lockToken)
m_spinlock.Exit(false);
}
}
}
private static void Work2(int TaskID)
{
int i = 0;
string log = "";
bool lockToken = false;
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
lockToken = false;
m_spinlock.TryEnter(2000, ref lockToken);
if (!lockToken)
{
Console.WriteLine("Work2 TIMEOUT!! Will throw Exception");
throw new TimeoutException("Work2 TIMEOUT!!");
}
_StrBlder.Append(log);
}
finally
{
if (lockToken)
m_spinlock.Exit(false);
}
}
}
private static void Work3(int TaskID)
{
int i = 0;
string log = "";
bool lockToken = false;
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
lockToken = false;
m_spinlock.TryEnter(2000, ref lockToken);
if (!lockToken)
{
Console.WriteLine("Work3 TIMEOUT!! Will throw Exception");
throw new TimeoutException("Work3 TIMEOUT!!");
}
_StrBlder.Append(log);
}
finally
{
if (lockToken)
m_spinlock.Exit(false);
}
}
}
static void Main(string[] args)
{
_Tasks = new Task[_TaskNum];
_StrBlder = new StringBuilder();
m_spinlock = new SpinLock();
_Tasks[0] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work1(taskid);
}, 0);
_Tasks[1] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work2(taskid);
}, 1);
_Tasks[2] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work3(taskid);
}, 2);
var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
{
Task.WaitAll(_Tasks);
Console.WriteLine("==========================================================");
Console.WriteLine("All Phase is completed");
Console.WriteLine("==========================================================");
Console.WriteLine(_StrBlder);
});
try
{
finalTask.Wait();
}
catch (AggregateException aex)
{
Console.WriteLine("Task failed And Canceled" + aex.ToString());
}
finally
{
}
Console.ReadLine();
}
}
}
使用基于自旋的等待
如果需要等待某个条件满足的时间很短,而且不希望发生上下文切换,基于自旋的【等待】是一种很好的解决方案。
- SpinWait : 自旋等待
- SpinUntil : 等待某个条件发生
如果发生了长时间的自旋,SpinWait会让出底层的时间片,并触发上下文切换。因为长时间的自旋会阻塞优先级更高的线程。当一个线程自旋时,它会将一个内核放入到一个繁忙的循环中,而且它不会让出处理器时间片的剩余部分。SpinWait的智能逻辑中会在自旋达到足够长的时间时停止自旋并让出处理器。当然可以考虑调用Thread.Sleep()方法,它会让出处理器时间,但开销比较大。
示例程序:这里通过使用SpinWait 来控制3个Task的执行顺序。
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Sample5_6_spinwait
{
class Program
{
private static int _TaskNum = 3;
private static Task[] _Tasks;
private static StringBuilder _StrBlder;
private const int RUN_LOOP = 10;
private static bool m_IsWork2Start = false;
private static bool m_IsWork3Start = false;
private static void Work1(int TaskID)
{
int i = 0;
string log = "";
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} =====\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
_StrBlder.Append(log);
}
finally
{
m_IsWork2Start = true;
}
}
}
private static void Work2(int TaskID)
{
int i = 0;
string log = "";
System.Threading.SpinWait.SpinUntil(() => m_IsWork2Start);
while ((i < RUN_LOOP) && (m_IsWork2Start))
{
log = String.Format("Time: {0} Task : #{1} Value: {2} *****\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
_StrBlder.Append(log);
}
finally
{
m_IsWork3Start = true;
}
}
}
private static void Work3(int TaskID)
{
int i = 0;
string log = "";
System.Threading.SpinWait.SpinUntil(() => m_IsWork3Start);
while (i < RUN_LOOP)
{
log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~\n",
DateTime.Now.TimeOfDay, TaskID, i);
i++;
try
{
_StrBlder.Append(log);
}
finally
{
}
}
}
static void Main(string[] args)
{
_Tasks = new Task[_TaskNum];
_StrBlder = new StringBuilder();
_Tasks[0] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work1(taskid);
}, 0);
_Tasks[1] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work2(taskid);
}, 1);
_Tasks[2] = Task.Factory.StartNew((num) =>
{
var taskid = (int)num;
Work3(taskid);
}, 2);
var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) =>
{
Task.WaitAll(_Tasks);
Console.WriteLine("==========================================================");
Console.WriteLine("All Phase is completed");
Console.WriteLine("==========================================================");
Console.WriteLine(_StrBlder);
});
try
{
finalTask.Wait();
}
catch (AggregateException aex)
{
Console.WriteLine("Task failed And Canceled" + aex.ToString());
}
finally
{
}
Console.ReadLine();
}
}
}
测试结果:
Time: 08:41:33.5505335 Task : #0 Value: 0 =====
Time: 08:41:33.5725357 Task : #0 Value: 1 =====
Time: 08:41:33.5725357 Task : #0 Value: 2 =====
Time: 08:41:33.5725357 Task : #0 Value: 3 =====
Time: 08:41:33.5725357 Task : #0 Value: 4 =====
Time: 08:41:33.5725357 Task : #0 Value: 5 =====
Time: 08:41:33.5725357 Task : #0 Value: 6 =====
Time: 08:41:33.5725357 Task : #0 Value: 7 =====
Time: 08:41:33.5725357 Task : #0 Value: 8 =====
Time: 08:41:33.5725357 Task : #0 Value: 9 =====
Time: 08:41:33.5735358 Task : #1 Value: 0 *
Time: 08:41:33.5735358 Task : #1 Value: 1 *
Time: 08:41:33.5735358 Task : #1 Value: 2 *
Time: 08:41:33.5735358 Task : #1 Value: 3 *
Time: 08:41:33.5735358 Task : #1 Value: 4 *
Time: 08:41:33.5735358 Task : #1 Value: 5 *
Time: 08:41:33.5735358 Task : #1 Value: 6 *
Time: 08:41:33.5735358 Task : #1 Value: 7 *
Time: 08:41:33.5735358 Task : #1 Value: 8 *
Time: 08:41:33.5735358 Task : #1 Value: 9 *
Time: 08:41:33.5735358 Task : #2 Value: 0 ~
Time: 08:41:33.5735358 Task : #2 Value: 1 ~
Time: 08:41:33.5735358 Task : #2 Value: 2 ~
Time: 08:41:33.5735358 Task : #2 Value: 3 ~
Time: 08:41:33.5735358 Task : #2 Value: 4 ~
Time: 08:41:33.5735358 Task : #2 Value: 5 ~
Time: 08:41:33.5735358 Task : #2 Value: 6 ~
Time: 08:41:33.5735358 Task : #2 Value: 7 ~
Time: 08:41:33.5735358 Task : #2 Value: 8 ~
Time: 08:41:33.5735358 Task : #2 Value: 9 ~
操作 SpinWait 的另一种方式 – 实例化 SpinWait。
SpinWait 提供了两个方法和两个只读属性。
方法:
- SpinWait.Reset() : 重置自旋计数器,将计数器置 0。效果就好像没调用过SpinOnce一样。
- SpinWait.Once() : 执行一次自旋。当SpinWait自旋达到一定次数后,如果有必要当前线程会让出底层的时间片并触发上下文切换。
属性:
- SpinWait.Count:方法执行单次自旋的次数。
- SpinWait.NextSpinWillYield:一个bool值,表示下一次通过SpinOnce方法自旋是否会让出底层线程的时间片并发生上下文切换。