lock关键字详解
2009-08-21 14:19
281 查看
[倾情原创] 锁·二则——lock关键字详解
收藏
document.body.oncopy = function() {if (window.clipboardData) {
setTimeout(function() {
var text = clipboardData.getData("text");
if (text && text.length>300) {
text = text + "/r/n/n本文来自CSDN博客,转载请标明出处:" + location.href;
clipboardData.setData("text", text);
}
}, 100);
}
}
function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}
法律声明:
本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。文章出处请务必注明
CSDN
以保障网站的权益,文章作者姓名请务必保留,
并向
bladey@tom.com
发送邮件,标明文章位置及用途。转载时请将此法律声明一并转载,谢谢!
锁·二则
作
者:刘铁猛
日
期:
2005-12-25
关键字:
lock
多线程
同步
小序
锁者,
lock
关键字也。市面上的书虽然多,但仔细介绍这个
keyword
的书太少了。
MSDN
里有,但所给的代码非常零乱,让人不能参透其中的玄机。昨天是平安夜,今天自然就是圣诞节了,没别的什么事情,于是整理了一下思路,使用两个例子给大家讲解一下
lock
关键字的使用和一点线程同步的问题。
一.基础铺垫——进程与线程
阅读提示:如果您已经了解什么是线程以及如何使用线程,只关心如何使用
lock
关键字,请跳过一、二、三节,直接登顶。
多线程(
multi-thread
)程序是
lock
关键字的用武之地。如果想编写多线程的程序,一般都要在程序的开头导入
System.Threading
这个名称空间。
一般初学者在看书的时候,一看到多线程一章就会跳过去,一是认为自己的小宇宙不够强、功力不够深——线程好神秘、好诡异——还是先练好天马流星拳之后再来收拾它;二是不了解什么时候要用到线程,认为自己还用不到那么高深的技术。其实呢,线程是个即简单又常用的概念。
1
.线程的载体——进程
要想知道什么是线程,就不得不先了解一下线程的载体——进程。
我们的程序在没有运行的时候,都是以可执行文件(
*.exe
文件)的形式静静地躺在硬盘里的。
Windows
下的可执行文件称为
PE
文件格式(
Portable Executable File Format
),这里的
Portable
不是指
Portable Computer
(便携式电脑,也就是本本)中的“便携”,而是指在所有
Windows
系统上都可以执行的便捷性、可移植性。可执行文件会一直那么静静地躺着,直到你用鼠标敲两下它的脑袋——这时候,
Windows
的程序管理功能就会根据程序的特征为程序分配一定的内存空间,并使用加载器(
loader
)把程序体装载入内存。不过值得注意的是
,这个时候程序虽然已经被装载入了内存,但还没有执行起来。接下来
Windows
会寻找程序的“入口点”以开始执行它。当然,我们都已经知道——如果是命令行应用程序,那么它的入口点是
main
函数
,如果是
GUI
(
Graphic User Interface
,简言之就是带窗口交互界面一类的)应用程序,那么它的入口点将是
_tWinMain
函数
(早先叫
WinMain
,参阅本人另一篇拙文《一个
Win32
程序的进化
》)。一旦找到这个入口点函数,操作系统就要“调用”这个主函数,程序就从这里进入执行了。同时,系统会把开始执行的应用程序注册为一个“进程”(
Process
),欲知系统运行着多少进程,你可以按下
Ctrl+Alt+Del
,从
Task Manager
里查看(如果对
Windows
如何管理进程感兴趣,可以阅读与分时系统、抢先式多任务相关的文章和书籍)。
至此,我们可以说:如果把一个应用程序看成是一个
Class
的话,那么进程就是这个
Class
在内存中的一个“活”的实例——这是面向对象的理念。现在或许你也应该明白了为什么
C#
语言的
Main
函数要写在一个
Class
里,而不能像
C/C++
那样把
main
函数赤裸裸地写在外面。类是可以有多个实例的,一个程序也可以通过被双击
N
次在内存中运行
N
个副本,我们常用的
Word 2003
、
等都是这样的程序。当然,也有的程序只允许在内存里有一个实例,
MSN Messenger
和杀毒软件就是是这样的一类。
2
.主角登场——线程
一个进程只做一件事情,这本无可非议,但无奈人总是贪心的。人们希望应用程序一边做着前台的程序,一边在后台默默无闻地干着其它工作。线程的出现,真可谓“将多任务进行到底”了。
这儿有几个实际应用的例子。比如我在用
Word
杜撰老板交给的命题(这是
Word
的主线程),我的
Word
就在后台为我计时,并且每
10
分钟为我自动保存一次,以便在发生地震之后我能快速找回十分钟之前写的稿子并继续工作——死不了还是要交的。抑或是我的
Outlook
,它一边看我向手头的邮件里狠命堆诸如“预算正常”“进展顺利”之类的字眼,一边不紧不慢地在后台接收别人发给我的债务单和催命会议通知……它哪里知道我是多么想到
Out
去
look
一下,透透气。诸此
IE
,
MSN Messenger
,
,
Flashget
,
BT
,
eMule
……尽数是基于多线程而得以生存的软件。现在,我们应该已经意识到,基本上稍微有点用的程序就得用到多线程——特别是在网络应用程序中,使用多线程格外重要。
二.进程和线程间的关系
我们已经在感观上对进程和线程有了初步的了解,那么它们之间有什么关系呢?前面我们已经提到一点,那就是——进程是线程的载体,每个进程至少包含一个线程
。接下来,我们来看看其它的关系。
1
.进程与进程的关系
:在
.NET
平台下,每个应用程序都被
load
进入自己独立的内存空间内,这个内存空间称为
Application Domain
,简称为
AppDomain
。一个一个
AppDomain
就像一个一个小隔间,把进程与进程、进程与系统底层之间隔绝起来,以防某个程序突然发疯的话会殃及近邻或者造成系统崩溃。
2
.线程与线程的关系
:
在同一个进程内可以存在很多线程,与进程同时启动的那个线程是主线程。非主线程不可能自己启动,一定是直接或间接由主线程启动的。线程与线程之间可以相互
通信,共同使用某些资源。每个线程具有自己的优先级,优先级高的先执行,低的后执行。众线之间的关系非有趣——如果它们之间是互相独立、谁也不用顾及谁的
话,那么就是“非同步状态”(
Unsynchronized
)
,比较像路上的行人;而如果线程与线程之间是相互协同协作甚至是依赖的,那么就是“同步状态”(
Synchronized
)
,这与反恐特警执行
Action
一样,需要互相配合,绝不能一哄而上——投手雷不能像招聘会上投简历那样!
三.线程小例
这里给出一个
C#
写的多线程的小范例,如果有哪里不明白,请参阅
MSDN
。我在以后的文章中将仔细讲解这些小例子。
//==============================================//
//
水之真谛
//
//
//
// http://blog.csdn.net/FantasiaX //
//
//
//
上善若水润物无声
//
//==============================================//
using
System;
using
System.Threading;//
多线程程序必需的
namespace
ThreadSample1
{
class
A
{
//
为了能够作为线程的入口点,程序必需是无参、无返回值
public
static
void
Say()
{
for
(int
i = 0; i < 1000; i++)
{
Console
.ForegroundColor = ConsoleColor
.Yellow;
Console
.WriteLine("A merry Christmas to you!"
);
}
}
}
class
B
{
//
为了能够作为线程的入口点,程序必需是无参、无返回值
public
void
Say()
{
for
(int
i = 0; i < 1000; i++)
{
Console
.ForegroundColor = ConsoleColor
.Green;
Console
.WriteLine("A merry Christmas to you!"
);
}
}
}
class
Program
{
static
void
Main(string
[] args)
{
//
用到两个知识点:A
类的静态方法;匿名的ThreadStart
实例
//
如果想了解如何构造一个线程(Thread
)请查阅MSDN
Thread
Thread1 = new
Thread
(new
ThreadStart
(A
.Say));
B
b = new
B
();
//
这次是使用实例方法来构造一个线程
Thread
Thread2 = new
Thread
(new
ThreadStart
(b.Say));
//
试试把下面两句同时注释掉,会发生什么结果?
Thread2.Priority = ThreadPriority
.Highest;
Thread1.Priority = ThreadPriority
.Lowest;
Thread1.Start();
Thread2.Start();
}
}
}
这个例子完全是为了我们讲解
lock
而做的铺垫,希望大家一定要仔细读懂。其中最重要的是理解由静态方法和实例方法构造线程。还要注意到,本例中使用到了线程的优先级:
Thread2
的优先级为最高,
Thread1
的优先级为最低,所以尽管
Thread1
比
Thread2
先启动,而要等到
Thread2
执行完之后再执行(线程优先级有
5
级,大家可以自己动手试一试)。如果把给两个线程赋优先级的语句注释掉,你会发现两种颜色交错在一起……这就体现出了线程间的“非同步状态”。注意:在没有注释掉两句之前,两个线程虽然有先后顺序,但那是由优先级(操作系统)决定的,不能算是同步(线程间相互协同)
。
四.登顶
很抱歉的一点是,
lock
的使用与线程的同步是相关的,而本文限于篇幅又不能对线程同步加以详述。本人将在近期再写一篇专门记述线程同步的文章,在此之前,请大家先参阅
MSDN
及其他同仁的作品。
1
.使用
lock
关键字的第一个目的:保证共享资源的安全
。
当多个线程共享一个资源的时候,常常会产生协同问题,这样的协同问题往往是由于时间延迟引起的。拿银行的
ATM
机举例,如果里面有可用资金
5000
元,每个人每次可以取
50
到
200
元,现在有
100
个人来取钱。假设一个人取钱的时候,
ATM
机与银行数据库的沟通时间为
10
秒,那么在与总行计算机沟通完毕之前(也就是把你取的钱从可用资金上扣除之前),
ATM
机不能再接受别一个人的请求——也就是被“锁定”。这也就是
lock
关键字
得名的原因。
如果不“锁定”
ATM
会出现什么情况呢?假设
ATM
里只剩下
100
元了,后面还有很多人等着取钱,一个人取
80
,
ATM
验证
80<100
成立,于是吐出
80
,这时需要
10
秒钟与银行总机通过网络沟通(网络,特别是为了保证安全的网络,总是有延迟的),由于没有锁定
ATM
,后面的客户也打算取
80
……戏剧性的一幕出现了:
ATM
又吐出来
80
!因为这时候它仍然认为自己肚子里有
100
元!下面的程序就是这个例子的完整实现。
这个例子同时也展现了
lock
关键第的第一种用法:针对由静态方法构造的线程,由于线程所执行的方法并不具有类的实例作为载体,所以,“上锁”的时候,只能是锁这个静态方法所在的类
——
lock
(typeof
(ATM
))
//======================================================//
//
水之真谛
//
//
//
// http://blog.csdn.net/FantasiaX //
//
//
//
上善若水润物无声
//
//======================================================//
using
System;
using
System.Threading;
namespace
LockSample
{
class
ATM
{
static
int
remain = 5000;//
可用金额
public
static
void
GiveOutMoney(int
money)
{
lock
(typeof
(ATM
))//
核心代码!注释掉这句,会得到红色警报
{
if
(remain >= money)
{
Thread
.Sleep(100);//
模拟时间延迟
remain -= money;
}
}
if
(remain >= 0)
{
Console
.ForegroundColor = ConsoleColor
.Green;
Console
.WriteLine("{0}$ /t in ATM."
, remain);
}
else
{
Console
.ForegroundColor = ConsoleColor
.Red;
Console
.WriteLine("{0}$ /t remained."
, remain);
}
}
}
class
Boy
{
Random
want = new
Random
();
int
money;
public
void
TakeMoney()
{
money = want.Next(50, 200);
ATM
.GiveOutMoney(money);
}
}
class
Program
{
static
void
Main(string
[] args)
{
Boy
[] Boys = new
Boy
[100];
Thread
[] Threads = new
Thread
[100];
for
(int
i = 0; i < 100; i++)
{
Boys[i] = new
Boy
();
Threads[i] = new
Thread
(new
ThreadStart
(Boys[i].TakeMoney));
Threads[i].Name = "Boy"
+ i.ToString();
Threads[i].Start();
}
}
}
}
2
.使用
lock
关键字的第二个目的:保证线程执行的顺序合理
。
回想上面的例子:取钱这件事情基本上可以认为是一个操作就能完成,而很多事情并不是一步就能完成的,特别是如果每一步都与某个共享资源挂钩时,如果在一件事情完成(比如十个操作步骤)之前不把资源锁进来,那么
N
多线程乱用资源,肯定会混乱不堪的。相反,如果我们在一套完整操作完成之前能够锁定资源(保证使用者的“独占性”
),那么想使用资源的
N
多线程也就变得井然有序了。
狗
年快到了,让我们来看看我们的狗妈妈是怎样照顾她的小宝贝的。狗妈妈“花花”有三个小宝贝,它们的身体状况不太相同:壮壮很壮,总是抢别人的奶吃;灵灵体
格一般,抢不到先也不会饿着;笨笨就比较笨了,身体弱,总是喝不着奶。这一天,狗妈妈决定改善一下给小宝贝们喂奶的方法——由原来的哄抢方式改为一狗喂十
口,先喂笨笨,然后是灵灵,最后才是壮壮……在一只小狗狗吮完十口之前,别的小狗狗不许来捣蛋!
OK
,让我们看下面的代码:
注意,这段代码展示了
lock
的第二种用法——针对由实例方法构造的线程,
lock
将锁住这个方法的实例载体,也就是使用了
——
lock
(this
)
//======================================================//
//
水之真谛
//
//
//
// http://blog.csdn.net/FantasiaX //
//
//
//
上善若水润物无声
//
//======================================================//
using
System;
using
System.Threading;
namespace
LockSample2
{
class
DogMother
{
//
喂一口奶
void
Feed()
{
//Console.ForegroundColor = ConsoleColor.Yellow;
//Console.WriteLine("Puzi...zi...");
//Console.ForegroundColor = ConsoleColor.White;
Thread
.Sleep(100);//
喂一口奶的时间延迟
}
//
每只狗狗喂口奶
public
void
FeedOneSmallDog()
{
//
因为用到了实例方法,所以要锁this
,this
是本类运行时的实例
//
注释掉下面一行,回到哄抢方式,线程的优先级将产生效果
lock
(this
)
{
for
(int
i = 1; i <= 10; i++)
{
this
.Feed();
Console
.WriteLine(Thread
.CurrentThread.Name.ToString() + " sucked {0} time."
, i);
}
}
}
}
class
Program
{
static
void
Main(string
[] args)
{
DogMother
huahua = new
DogMother
();
Thread
DogStrong = new
Thread
(new
ThreadStart
(huahua.FeedOneSmallDog));
DogStrong.Name = "Strong small Dog"
;
DogStrong.Priority = ThreadPriority
.AboveNormal;
Thread
DogNormal = new
Thread
(new
ThreadStart
(huahua.FeedOneSmallDog));
DogNormal.Name = "Normal small Dog"
;
DogNormal.Priority = ThreadPriority
.Normal;
Thread
DogWeak = new
Thread
(new
ThreadStart
(huahua.FeedOneSmallDog));
DogWeak.Name = "Weak small Dog"
;
DogWeak.Priority = ThreadPriority
.BelowNormal;
//
由于lock
的使用,线程的优先级就没有效果了,保证了顺序的合理性
//
注释掉lock
句后,线程的优先级将再次显现效果
DogWeak.Start();
DogNormal.Start();
DogStrong.Start();
}
}
}
小结:
祝贺你!
至此,我们已经初步学会了如何使用
C#
语言的
lock
关键字来使一组共享同一资源的线程进行同步、保证执行顺序的合理以及共享资源的安全。
相信如果你已经仔细看过例子,并在自己的机器上进行了实践,那么你对线程已经不再陌生、害怕(就像小宇宙爆发了一样)。如果你不满足于仅仅是学会一个
lock
,还想掌握更多更高超的技能(比如……呃……六道轮回?柔破斩?无敌风火轮?如来神掌?),请参阅
MSDN
中
System.Threading
名称空间的内容,你会发现
lock
背后隐藏的秘密(
Monitor
类
),而且我也极力推荐你这么做:趁热打铁,你可以了解到为什么
lock
只能对引用类型加以使用
、
lock
与
Monitor
的
Enter/Exit
和
Try…Catch
是如何互换的……
五.祝愿
今天是圣诞节呢,祝所有看到本文的
XDJM
圣诞节快乐!新年也快乐!
再过几天,新年也要到啦!狗狗年!我喜欢……祝所有明年是本命年的
XDJM
生活幸福,万事如意!噢
~~~~~
对了,明年是大狗高立的本命年,借这当先祝你吉祥如意啦!
六.祈福
我
有一个好朋友,小迪,心脏不太好……明年还要动大手术。在这离上帝最近的日子里,我为她祈祷:小迪,祝你平安!一切都会好起来的!我祈求上帝:当我在圣诞
节的时候把我知道的知识尽心尽力讲给每一个愿意听的人时,算不算是帮您做事呢?如果是……请您赐予小迪以健康。我同样祈求每个读到这里的朋友:请您也为我
的朋友祝福一下,谢谢。
相关文章推荐
- 锁·——lock关键字详解
- 锁·二则——lock关键字详解 (转)
- 锁·——lock关键字详解
- [倾情原创] 锁·二则——lock关键字详解
- [倾情原创] 锁·二则——lock关键字详解
- [倾情原创] 锁·二则——lock关键字详解
- 锁·——lock关键字详解
- 锁·二则——lock关键字详解
- 转:c# 线程同步: 详解lock,monitor,同步事件和等待句柄以及mutex
- C# 多线程详解 Part.04(Lock、Monitor、生产与消费)
- React Native关键字详解
- C/C++中extern关键字详解
- C/C++之 extern关键字详解
- C/C++static关键字详解
- JAVA关键字package/import详解
- C++11 并发指南三(Lock 详解)
- 【线程同步学习笔记】C#中的lock关键字
- C/C++中extern关键字详解
- lock关键字用法
- java synchronized关键字详解