32. C# -- 线程和进程 (2)

前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:

lock(expression) statement_block

expression代表你希望跟踪的对象,通常是对象引用。

  • 如果你想保护一个类的实例,一般地,你可以使用this;
  • 如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

下面是一个使用lock关键字的典型例子,在注释里说明了lock关键字的用法和用途。

示例如下:

using System;
using System.Threading;

namespace ThreadSimple
{
    internal class Account 
    {
        int balance;
        Random r = new Random();
        
        internal Account(int initial) 
        {
            balance = initial;
        } 
        internal int Withdraw(int amount) 
        {
            if (balance < 0)
            {
                //如果balance小于0则抛出异常
                throw new Exception("Negative Balance");
            }
            //下面的代码保证在当前线程修改balance的值完成之前
            //不会有其他线程也执行这段代码来修改balance的值
            //因此,balance的值是不可能小于0 的
            lock (this)
            {
                Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);
                //如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
                //另外一个线程却执行了balance=balance-amount修改了balance的值
                //而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
                //但是,这个线程却继续执行balance=balance-amount,所以导致balance可能小于0
                if (balance >= amount) 
                {
                    Thread.Sleep(5);
                    balance = balance - amount;
                    return amount;
                } 
                else 
                {
                    return 0; // transaction rejected
                  }
            }
        }
        internal void DoTransactions() 
        {
            for (int i = 0; i < 100; i++) 
            Withdraw(r.Next(-50, 100));
        }
    } 
    internal class Test 
    {
        static internal Thread[] threads = new Thread[10];
        public static void Main() 
        {
            Account acc = new Account (0);
            for (int i = 0; i < 10; i++) 
            {
                Thread t = new Thread(new ThreadStart(acc.DoTransactions));
                threads[i] = t;
            }
            for (int i = 0; i < 10; i++) 
                threads[i].Name=i.ToString();
            for (int i = 0; i < 10; i++) 
                threads[i].Start();
            Console.ReadLine();
        }
    }
}

Monitor 类锁定一个对象

当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。

Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。 Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形:

......

Queue oQueue=new Queue();

......

Monitor.Enter(oQueue);

......//现在oQueue对象只能被当前线程操纵了

Monitor.Exit(oQueue);//释放锁

如上所示,当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-catch-finally结构中的finally代码块里。

对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息:

其一是现在持有锁的线程的引用;

其二是一个预备队列,队列中保存了已经准备好获取锁的线程;

其三是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。

当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。

下面是一个展示如何使用lock关键字和Monitor类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。

这个例程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示(注释中介绍了该程序的精要所在)。

用到的系统命名空间如下:

using System;

using System.Threading;

首先,定义一个被操作的对象的类Cell,在这个类里,有两个方法:ReadFromCell()和WriteToCell。消费者线程将调用ReadFromCell()读取cellContents的内容并且显示出来,生产者进程将调用WriteToCell()方法向cellContents写入数据。

示例如下:

public class Cell
{
        int cellContents; // Cell对象里边的内容
        bool readerFlag = false; // 状态标志,为true时可以读取,为false则正在写入
        public int ReadFromCell( )
        {
            lock(this) // Lock关键字保证了什么,请大家看前面对lock的介绍
            {
                if (!readerFlag)//如果现在不可读取
                { 
                    try
                    {
                        //等待WriteToCell方法中调用Monitor.Pulse()方法
                        Monitor.Wait(this);
                    }
                    catch (SynchronizationLockException e)
                    {
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedException e)
                    {
                        Console.WriteLine(e);
                    }
                }
                Console.WriteLine("Consume: {0}",cellContents);
                readerFlag = false;
                //重置readerFlag标志,表示消费行为已经完成
                Monitor.Pulse(this); 
                //通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
            }
            return cellContents;
        }
    
        public void WriteToCell(int n)
        {
            lock(this)
            {
                if (readerFlag)
                {
                    try
                    {
                        Monitor.Wait(this);
                    }
                    catch (SynchronizationLockException e)
                    {
                            //当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
                        Console.WriteLine(e);
                    }
                    catch (ThreadInterruptedException e)
                    {
                            //当线程在等待状态的时候中止 
                        Console.WriteLine(e);
                    }
                }
                cellContents = n;
                Console.WriteLine("Produce: {0}",cellContents);
                readerFlag = true; 
                Monitor.Pulse(this); 
                //通知另外一个线程中正在等待的ReadFromCell()方法
            }
        }
}

下面定义生产者类 CellProd 和消费者类 CellCons ,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口。

public class CellProd
{
      Cell cell; // 被操作的Cell对象
      int quantity = 1; // 生产者生产次数,初始化为1 

      public CellProd(Cell box, int request)
      {
        //构造函数
        cell = box; 
        quantity = request; 
      }
      public void ThreadRun( )
      {
        for(int looper=1; looper<=quantity; looper++)
            cell.WriteToCell(looper); //生产者向操作对象写入信息
      }
}

public class CellCons
{
      Cell cell; 
      int quantity = 1; 

      public CellCons(Cell box, int request)
      {
                //构造函数
        cell = box; 
        quantity = request; 
      }
      public void ThreadRun( )
      {
        int valReturned;
        for(int looper=1; looper<=quantity; looper++)
            valReturned=cell.ReadFromCell( );//消费者从操作对象中读取信息
      }
}

然后在下面这个类MonitorSample的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。

public class MonitorSample
{
      public static void Main(String[] args)
      {
       int result = 0; //一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
       Cell cell = new Cell( );

//下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
       CellProd prod = new CellProd(cell, 20);
       CellCons cons = new CellCons(cell, 20);

Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
       Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
       //生产者线程和消费者线程都已经被创建,但是没有开始执行
       try
       {
        producer.Start( );
        consumer.Start( );

        producer.Join( );
        consumer.Join( );
        Console.ReadLine();
       }
       catch (ThreadStateException e)
       {
        //当线程因为所处状态的原因而不能执行被请求的操作
        Console.WriteLine(e);
        result = 1;
       }
       catch (ThreadInterruptedException e)
       {
        //当线程在等待状态的时候中止
        Console.WriteLine(e);
        result = 1;
       }
       //尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
       Environment.ExitCode = result;
      }
}

在上面的例程中,同步是通过等待Monitor.Pulse()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Pulse)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Pulese()发出的“脉冲”。

它的执行结果很简单:

Produce: 1

Consume: 1

Produce: 2

Consume: 2

Produce: 3

Consume: 3

...

...

Produce: 20

Consume: 20

事实上,这个简单的例子已经帮助我们解决了多线程应用程序中可能出现的大问题,只要领悟了解决线程间冲突的基本方法,很容易把它应用到比较复杂的程序中去。

参考:

http://kb.cnblogs.com/page/42527/

时间: 2024-10-08 09:48:07

32. C# -- 线程和进程 (2)的相关文章

Python基础—线程、进程和协程

今天已是学习Python的第十一天,来干一碗鸡汤继续今天的内容,今天的鸡汤是:超越别人对你的期望.本篇博客主要介绍以下几点内容: 线程的基本使用: 线程的锁机制: 生产者消费之模型(队列): 如何自定义线程池: 进程的基本使用: 进程的锁机制: 进程之间如何实现数据共享: 进程池: 协程的基本使用. 一.线程 1.创建线程 上篇博客已经介绍过如何创建多线程的程序,在这里在复习一下如何创建线程过程以及线程的一些方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1

python线程、进程和协程

链接:http://www.jb51.net/article/88825.htm 引言 解释器环境:python3.5.1 我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句: server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer) Threadi

iOS开发:线程和进程

iOS开发多线程篇—多线程简单介绍 一.进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内 比如同时打开QQ.Xcode,系统就会分别启动2个进程 通过“活动监视器”可以查看Mac系统中所开启的进程 2.什么是线程 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程) 线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行 比如使用酷狗播放音乐.使用迅雷下载电影,都需要在线程中执行 3.线程

Python-网络编程之线程与进程

一.线程与进程的区别 线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间.当进程退出时该进程所产生的线程都会被强制退出并清除.线程可与属于同一进程的其他线程共享进程所拥有的全部资源,但是起本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器,一组寄存器和栈). 进程是正在执行的程序实例.进程至少由一个线程组成,线程是最小的执行单元. 二.threading模块 Threading方法: t.start() # 激活线程 t.getName() # 获

python之线程、进程

进程:资源的集合 线程:操作CPU的最小调试单位 最简单的多线程实例如下: #!/usr/bin/python #Author:sean #线程有2种调用方式,如下: #直接调用 import threading import time def run(n):     print("task ",n)     time.sleep(2) if __name__ == '__main__':     t1 = threading.Thread(target=run,args=("

Python网络编程之线程与进程

What is a Thread? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 在同一个进程内的线程的数据是可以进行互相访问的. 线程的切换使用过上下文来实现的,比如有一本书,有a和b这两个人(两个线程)看,a看完之后记录当前看到那一页哪一行,然后交给b看,b看完之后记录当前看到了那一页哪一行,此时a又要看了,那么a就通过上次记录的值(上下文)直接找到上次

001-多线程基础-进程线程、线程状态、优先级、用户线程和守护线程

一.进程与线程 1.DOS系统[单进程系统] 最早的时候DOS有一个特点:只要电脑有病毒,那么电脑就死机了. 原因:传统的DOS系统属于单进程系统,即:在同一时间段内只允许有一个程序运行. 2.Windows系统[多进程多线程] 电脑中毒也可以运行,但是会变慢 原因:因为在一个cpu.一块资源的情况下,程序利用一些轮转算法,可以让一个资源在一个时间段可以同时处理多个程序(进程),但是在一个时间点上只允许一个进程去执行. windows:任务管理器 linux:ps 在每一个进程上可以划分出若干个

进击的Python【第九章】:paramiko模块、线程与进程、各种线程锁、queue队列、生产者消费者模型

一.paramiko模块 他是什么东西? paramiko模块是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的连接. 先来个实例: 1 import paramiko 2 # 创建SSH对象 3 ssh = paramiko.SSHClient() 4 5 # 允许连接不在know_hosts文件中的主机 6 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 7 # 连接服务器 8 ss

python之线程、进程和协程

引言 解释器环境:python3.5.1我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句:server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer)ThreadingTCPServer这个类是一个支持多线程和TCP协议的socketserver,它的