作  者:刘铁猛
日  期: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、QQ等都是这样的程序。当然,也有的程序只允许在内存里有一个实例,MSN Messenger和杀毒软件就是是这样的一类。
2.主角登场——线程
    
一个进程只做一件事情,这本无可非议,但无奈人总是贪心的。人们希望应用程序一边做着前台的程序,一边在后台默默无闻地干着其它工作。线程的出现,真可谓“将多任务进行到底”了。
    这儿有几个实际应用的例子。比如我在用Word杜撰老板交给的命题(这是Word的主线程),我的Word就在后台为我计时,并且每10分钟为我自动保存一次,以便在发生地震之后我能快速找回十分钟之前写的稿子并继续工作——死不了还是要交的。抑或是我的Outlook,它一边看我向手头的邮件里狠命堆诸如“预算正常”“进展顺利”之类的字眼,一边不紧不慢地在后台接收别人发给我的债务单和催命会议通知……它哪里知道我是多么想到Out去look一下,透透气。诸此IE,MSN
Messenger,QQ,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是如何互换的……

法律声明:本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。文章出处请务必注明CSDN以保障网站的权益,文章作者姓名请务必保留,并向bladey@tom.com发送邮件,标明文章位置及用途。转载时请将此法律声明一并转载,谢谢!

原文链接:http://blog.csdn.net/fantasiax/article/details/561797   刘铁猛

锁·——lock关键字详解的更多相关文章

  1. java 锁 Lock接口详解

    一:java.util.concurrent.locks包下常用的类与接口(lock是jdk 1.5后新增的) (1)Lock和ReadWriteLock是两大锁的根接口,Lock代表实现类是Reen ...

  2. Java多线程(三)—— synchronized关键字详解

    一.多线程的同步 1.为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源.必须对这种潜在资源冲突进行预防. 解决方法:在线程使用一个资源时为其加锁即可. 访问资 ...

  3. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. Java synchronized 关键字详解

    Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...

  5. 【转载】C/C++中extern关键字详解

    1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...

  6. python关键字详解

    今天依旧在啃:<笨方法学python>,其中习题37是复习各种关键字.我本想百度一下记一下就ok了,但是百度出来第一个就Hongten的博客.我才意识到我也有博客,我应该学习他,把这些积累 ...

  7. Java面试题04-final关键字详解

    Java面试题04-final关键字详解 本篇博客将会讨论java中final关键字的含义,以及final用在什么地方,感觉看书总会有一些模糊,而且解释的不是很清楚,在此做个总结,以备准备面试的时候查 ...

  8. Objective-C 实用关键字详解1「面试、工作」看我就 🐒 了 ^_^.

    在写项目 或 阅读别人的代码(一些优秀的源码)中,总能发现一些常见的关键字,随着编程经验的积累大部分还是知道是什么意思 的. 相信很多开发者跟我当初一样,只是基本的常用关键字定义属性会使用,但在关键字 ...

  9. java continue break 关键字 详解 区别 用法 标记 标签 使用 示例 联系

    本文关键词: java continue break 关键字 详解 区别  用法 标记  标签 使用 示例 联系   跳出循环 带标签的continue和break 嵌套循环  深入continue ...

随机推荐

  1. 浅谈css盒模型

    在我们网页上的每一个元素,一个按钮,一段文本,一张图片等等,浏览器都将它们当做一个“盒子”看待,并把这样的盒子称为盒模型(box model).使用Chrome的右键>审查元素对某个网页上的元素 ...

  2. Spring中自己主动装配

    自己主动装配 在我们了解过constructor-arg和property装配中.都须要配置对应的属性和值或者引用,假设在比較复杂的项目中.就会使得XML的配置变得复杂,自己主动装配能够使用较少的配置 ...

  3. Robotium双client測试框架

    互联网的本质就是信息交换.移动互联网更是如此, 所以很多移动互联网的服务类应用中有着身份地位不同的两种用户(比如:交易中的买家和卖家, 教学中的老师和学生, 打车中的车主和乘客).近期的工作是给公司的 ...

  4. strcpy()、memcpy()、memmove()、memset()的内部实现

    一直想知道 strcpy().memcpy().memmove().memset()的内部实现 strcpy(), 字符串拷贝. char *strcpy(char *strDest, const c ...

  5. 扑克k,你知道的人物吗?

    在扑克牌中,黑桃K——大卫王(以色列联合王国第二任国王):红桃K——查里曼大帝(法兰克国王,后加冕为罗马人的皇帝):梅花K——亚历山大大帝(马其顿帝国国王):方块K——凯撒大帝(罗马共和国终生执政官) ...

  6. KeystoneJS+mongo搭建简易博客

    KeystoneJS 是一款基于 Express 和 MongoDB 的开源免费 Node.js CMS 网站开发框架. 一. 安装node.js,mongodb 二. 命令行安装KeystoneJS ...

  7. Maven 搭建与my-app项目测试

    前提条件,安装jdk1.6及以上版本,并配置JAVA_HOME 首先,下载Maven3.2.2,附下载地址:http://mirror.bit.edu.cn/apache/maven/maven-3/ ...

  8. Properties文件的XML格式(转)

    想必大家都用过*.properties文件,作为配置文件.但是,如果该文件写入了中文,待编译后内容就会成为乱码,使用native命令也好.使用ant执行编码转换也好,多少有点麻烦,与其如此,我们不如直 ...

  9. php获取文章内容中的全部图片数组

    <?php $pattern="/<img.*?src=[\'|\"](.*?(?:[\.gif|\.jpg]))[\'|\"].*?[\/]?>/&q ...

  10. 加密传输SSL协议3_非对称加密

    困死了,这里对非对称加密体系开个头,具体的实验明天写 非对称加密体系 为了解决对称加密中密钥的传输的问题,一些天才的数学家就提出了非对称式的加密体系,也称为公钥加密体系. 加密和解密的密钥是不同的.一 ...