分类: Windows平台
2015-08-12 10:29:07
摘要: 本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。
从这篇开始,在线程同步的方法上,开始在.NET平台上做个总结,同时对比Windows原生的API方法。你可以发现其中的联系。
相信很多看官早已对此十分熟悉了。本文作为总结性的文章,有一些篇幅将对比Monitor和关键段的关系。由于lock就是Monitor,所以先从Monitor说起,通常Monitor是像下面这样使用的:
Monitor.Entry(lockObj);
try
{
// lockObj的同步区
}
catch
(Exception e)
{
// 异常处理代码
}
finally
{
Monitor.Exit(lockObj);
// 解除锁定
}
当某个线程在Monitor.Entry返回后就获得了对其中lockObj的访问权限,其他试图获取lockObj的线程将被阻塞,直到线程调用Monitor.Exit释放lockObj的所有权。这意味着下面三点:
事实上其中的第二点是个重要的特征,这种情况将发生在递归的情况下。Monitor应该会记录线程获准访问lockObj的次数,以正确的对锁定次数进行递减。
我花了一些时间研究Monitor到底对应底层是什么实现方式,但是我并没有找到证据证明Monitor和关键段有什么必然联系。但是从表象上看,Monitor的API方式和关键段如此相似,而且上述的三个特点也几乎完全一致,况且MSDN也把Monitor表述成Critical Section,因此,暂且认为Monitor就是关键段的包装吧!
在我之前的文章【Windows】线程漫谈——线程同步之关键段中详细介绍了Windows API关键段,下面列出这两种API的对比:
.NET Monitor API | Windows API |
Monitor.Entry(lockObj) | EnterCriticalSection(&cs) |
Monitor.Exit(lockObj) | LeaveCriticalSection(&cs) |
Monitor.TryEntry(lockObj) | TryEnterCriticalSection(&cs) |
-- | InitializeCriticalSection(&cs); |
-- | DeleteCriticalSection(&cs); |
-- | InitializeCriticalSectionAndSpinCount |
-- | SetCriticalSectionSpinCount |
Monitor.Pulse | -- |
Monitor.Wait | -- |
可以看到Monitor简化了关键段的使用,而且还提供了额外的Wait和Pulse方法(因为不常用,因此这里不展开了)。但是如果Monitor真的就是关键段实现的话,Monitor却不能让我们设置旋转锁的尝试次数,这是一个缺陷。
关于Wait和Pulse顺便提一下,我个人认为是条件变量的一个替代方案。关于条件变量详见【Windows】线程漫谈——线程同步之Slim读/写锁。
最后再次强调,这里的对比只是本人一厢情愿,未必说Monitor真的就是关键段!
针对Monitor锁定的lockObj有如下问题需要注意:
public
class
Foo
{
public
void
Bar()
{
lock
(
this
)
{
Console.WriteLine(
"Class:Foo:Method:Bar"
);
}
}
}
public
class
MyClient
{
public
void
Test()
{
Foo f =
new
Foo();
lock
(f)
//获准了f对象
{
ThreadStart ts =
new
ThreadStart(f.Bar);
Thread t =
new
Thread(ts);
t.Start();
//新线程执行Bar方法需要获得f的访问权限,但是已被当前线程锁定,新线程将阻塞
t.Join();
//新线程将无法返回,死锁
}
}
}
public
class
Foo
{
public
void
Bar()
{
lock
(
"Const"
)
//Const将驻留
{
Console.WriteLine(
"Class:Foo:Method:Bar"
);
}
}
}
public
class
MyClient
{
private
string
lockObj =
"Const"
;
public
void
Test()
{
Foo f =
new
Foo();
lock
(lockObj)
//由于lockObj是"Const","Const"被驻留,所以实际上lock是同一个对象
{
ThreadStart ts =
new
ThreadStart(f.Bar);
Thread t =
new
Thread(ts);
t.Start();
//新线程执行Bar方法需要获得lockObj的访问权限,但是已被当前线程锁定,新线程将阻塞
t.Join();
//新线程将无法返回,死锁
}
}
}
上面两个例子已经用了lock而不是Monitor,事实上,lock经过编译后就是Monitor,但是lock无法使用Monitor.TryEntry:
.
try
{
...
IL_0037: call
void
[mscorlib]System.Threading.Monitor::Enter(
object
,
bool
&)
...
}
// end .try
finally
{
...
IL_0069: call
void
[mscorlib]System.Threading.Monitor::Exit(
object
)
...
}
最后,设计一个简单的带一个缓冲队列的Log方法,要求线程安全,下面给出C#的实现(在前面的【Windows】线程漫谈——线程同步之关键段利用关键段给出了C++的实现,这里的代码结构几乎一样,注释就省略了):
public
class
LogInfo
{
public
int
Level{
get
;
set
;}
public
string
Message{
get
;
set
;}
}
public
class
Log
{
private
static
List<LogInfo> LogQueue =
new
List<LogInfo>();
private
static
object
_lockLog =
new
object
();
private
static
object
_lockQueue =
new
object
();
public
void
Log(
int
Level,
string
Message)
{
if
(Monitor.TryEnter(_lockLog))
{
Monitor.Enter(_lockQueue);
foreach
(
var
l
in
LogQueue)
{
LogInternal(l.Level, l.Message);
}
LogQueue.Clear();
Monitor.Exit(_lockQueue);
LogInternal(Level, Message);
Monitor.Exit(_lockLog);
}
else
{
Monitor.Enter(_lockQueue);
LogQueue.Add(
new
LogInfo {
Level = Level,
Message = Message
});
Monitor.Exit(_lockQueue);
}
}
protected
virtual
void
LogInternal(
int
Level,
string
Message)
{
//真实的log动作可能会耗费非常长的时间
}
}
劳动果实,转载请注明出处:http://www.cnblogs.com/P_Chou/archive/2012/07/18/monitor-in-net-thread-sync.html