C#的多线程机制探索 不指定

Category : DotNet技术 | Post on 2008/03/25 11:47 by gdgzboy | Comments:3
      [本文来源于互联网]
  一.多线程的概念

  Windows是一个多任务的系统,如果你使用的是windows 2000及其以上版本,你可以通过任务管理器查看当前系统运行的程序和进程。什么是进程呢?当一个程序开始运行时,它就是一个进程,进程所指包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。浏览器就是一个很好的多线程的例子,在浏览器中你可以在下载JAVA小应用程序或图象的同时滚动页面,在访问新页面时,播放动画和声音,打印文件等。

  多线程的好处在于可以提高CPU的利用率——任何一个程序员都不希望自己的程序很多时候没事可干,在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

  然而我们也必须认识到线程本身可能影响系统性能的不利方面,以正确使用线程:

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多

  • 多线程需要协调和管理,所以需要CPU时间跟踪线程

  • 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题

  • 线程太多会导致控制太复杂,最终可能造成很多Bug


  •   基于以上认识,我们可以一个比喻来加深理解。假设有一个公司,公司里有很多各司其职的职员,那么我们可以认为这个正常运作的公司就是一个进程,而公司里的职员就是线程。一个公司至少得有一个职员吧,同理,一个进程至少包含一个线程。在公司里,你可以一个职员干所有的事,但是效率很显然是高不起来的,一个人的公司也不可能做大;一个程序中也可以只用一个线程去做事,事实上,一些过时的语言如fortune,basic都是如此,但是象一个人的公司一样,效率很低,如果做大程序,效率更低——事实上现在几乎没有单线程的商业软件。公司的职员越多,老板就得发越多的薪水给他们,还得耗费大量精力去管理他们,协调他们之间的矛盾和利益;程序也是如此,线程越多耗费的资源也越多,需要CPU时间去跟踪线程,还得解决诸如死锁,同步等问题。总之,如果你不想你的公司被称为“皮包公司”,你就得多几个员工;如果你不想让你的程序显得稚气,就在你的程序里引入多线程吧!

      本文将对C#编程中的多线程机制进行探讨,通过一些实例解决对线程的控制,多线程间通讯等问题。为了省去创建GUI那些繁琐的步骤,更清晰地逼近线程的本质,下面所有的程序都是控制台程序,程序最后的Console.ReadLine()是为了使程序中途停下来,以便看清楚执行过程中的输出。

      好了,废话少说,让我们来体验一下多线程的C#吧!

      二.操纵一个线程

      任何程序在执行时,至少有一个主线程,下面这段小程序可以给读者一个直观的印象:
    //SystemThread.cs
    using System;
    using System.Threading;

    namespace ThreadTest
    {
      class RunIt
      {
        [STAThread]
        static void Main(string[] args)
        {
          Thread.CurrentThread.Name="System Thread";//给当前线程起名为"System Thread"
    Console.WriteLine(Thread.CurrentThread.Name+"'Status:"+Thread.CurrentThread.ThreadState);
          Console.ReadLine();
        }
      }
    }

      编译执行后你看到了什么?是的,程序将产生如下输出:
    System Thread's Status:Running

      在这里,我们通过Thread类的静态属性CurrentThread获取了当前执行的线程,对其Name属性赋值“System Thread”,最后还输出了它的当前状态(ThreadState)。所谓静态属性,就是这个类所有对象所公有的属性,不管你创建了多少个这个类的实例,但是类的静态属性在内存中只有一个。很容易理解CurrentThread为什么是静态的——虽然有多个线程同时存在,但是在某一个时刻,CPU只能执行其中一个。

      就像上面程序所演示的,我们通过Thread类来创建和控制线程。注意到程序的头部,我们使用了如下命名空间:  
    using System;
      using System.Threading;

      在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。其中提供Thread类用于创建线程,ThreadPool类用于管理线程池等等,此外还提供解决了线程执行安排,死锁,线程间通讯等实际问题的机制。如果你想在你的应用程序中使用多线程,就必须包含这个类。Thread类有几个至关重要的方法,描述如下:

    引用
    Start():启动线程
    Sleep(int):静态方法,暂停当前线程指定的毫秒数
    Abort():通常使用该方法来终止一个线程
    Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复。
    Resume():恢复被Suspend()方法挂起的线程的执行


      下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。线程入口使程序知道该让这个线程干什么事,在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。

      打开你的VS.net,新建一个控制台应用程序(Console Application),下面这些代码将让你体味到完全控制一个线程的无穷乐趣!  
    //ThreadTest.cs

      using System;
      using System.Threading;

      namespace ThreadTest
      {
      public class Alpha
        {
          public void Beta()
          {
            while (true)
            {
              Console.WriteLine("Alpha.Beta is running in its own thread.");
            }
          }
        };

        public class Simple
        {
          public static int Main()
          {
            Console.WriteLine("Thread Start/Stop/Join Sample");

            Alpha oAlpha = new Alpha();
            file://这里创建一个线程,使之执行Alpha类的Beta()方法
            Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
            oThread.Start();
            while (!oThread.IsAlive);
              Thread.Sleep(1);
            oThread.Abort();
            oThread.Join();
            Console.WriteLine();
            Console.WriteLine("Alpha.Beta has finished");
            try
            {
              Console.WriteLine("Try to restart the Alpha.Beta thread");
              oThread.Start();
            }
            catch (ThreadStateException)
            {
              Console.Write("ThreadStateException trying to restart Alpha.Beta. ");
              Console.WriteLine("Expected since aborted threads cannot be restarted.");
              Console.ReadLine();
            }
            return 0;
          }
        }
      }

      这段程序包含两个类Alpha和Simple,在创建线程oThread时我们用指向Alpha.Beta()方法的初始化了ThreadStart代理(delegate)对象,当我们创建的线程oThread调用oThread.Start()方法启动时,实际上程序运行的是Alpha.Beta()方法:
      Alpha oAlpha = new Alpha();
      Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
      oThread.Start();

      然后在Main()函数的while循环中,我们使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程oThread。然后我们试图用Thread.Abort()方法终止线程oThread,注意后面的oThread.Join(),Thread.Join()方法使主线程等待,直到oThread线程结束。你可以给Thread.Join()方法指定一个int型的参数作为等待的最长时间。之后,我们试图用Thread.Start()方法重新启动线程oThread,但是显然Abort()方法带来的后果是不可恢复的终止线程,所以最后程序会抛出ThreadStateException异常。

      程序最后得到的结果将如下图:
    点击在新窗口中浏览此图片

      在这里我们要注意的是其它线程都是依附于Main()函数所在的线程的,Main()函数是C#程序的入口,起始线程可以称之为主线程,如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。而所有的线程虽然在微观上是串行执行的,但是在宏观上你完全可以认为它们在并行执行。

      读者一定注意到了Thread.ThreadState这个属性,这个属性代表了线程运行时状态,在不同的情况下有不同的值,于是我们有时候可以通过对该值的判断来设计程序流程。ThreadState在各种情况下的可能取值如下:

    引用
    Aborted:线程已停止
    AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止
    Background:线程在后台执行,与属性Thread.IsBackground有关
    Running:线程正在正常运行
    Stopped:线程已经被停止
    StopRequested:线程正在被要求停止
    Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)
    SuspendRequested:线程正在要求被挂起,但是未来得及响应
    Unstarted:未调用Thread.Start()开始线程的运行
    WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

      上面提到了Background状态表示该线程在后台运行,那么后台运行的线程有什么特别的地方呢?其实后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,CLR(通用语言运行环境)将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。

      当线程之间争夺CPU时间时,CPU按照是线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。给一个线程指定优先级
    ,我们可以使用如下代码:
      //设定优先级为最低
      myThread.Priority=ThreadPriority.Lowest;

      通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。

      现在我们对怎样创建和控制一个线程已经有了一个初步的了解,下面我们将深入研究线程实现中比较典型的的问题,并且探讨其解决方法。

      三.线程的同步和通讯——生产者和消费者

      假设这样一种情况,两个线程同时维护一个队列,如果一个线程对队列中添加元素,而另外一个线程从队列中取用元素,那么我们称添加元素的线程为生产者,称取用元素的线程为消费者。生产者与消费者问题看起来很简单,但是却是多线程应用中一个必须解决的问题,它涉及到线程之间的同步和通讯问题。

      前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。但是多线程环境下,可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:
      lock(expression) statement_block

      expression代表你希望跟踪的对象,通常是对象引用。一般地,如果你想保护一个类的实例,你可以使用this;如果你希望保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

      下面是一个使用lock关键字的典型例子,我将在注释里向大家说明lock关键字的用法和用途:
      //lock.cs
      using System;
      using System.Threading;

      internal class Account
      {
      int balance;
      Random r = new Random();
      internal Account(int initial)
      {
        balance = initial;
      }

      internal int Withdraw(int amount)
      {
        if (balance < 0)
        {
        file://如果balance小于0则抛出异常
        throw new Exception("Negative Balance");
        }
        //下面的代码保证在当前线程修改balance的值完成之前
        //不会有其他线程也执行这段代码来修改balance的值
        //因此,balance的值是不可能小于0的
        lock (this)
        {
        Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);
        file://如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
        file://另外一个线程却执行了balance=balance-amount修改了balance的值
        file://而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
        file://但是,这个线程却继续执行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();
      }
      }

      而多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用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
          {
          file://等待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; file://重置readerFlag标志,表示消费行为已经完成
        Monitor.Pulse(this); file://通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
        }
        return cellContents;
      }

      public void WriteToCell(int n)
      {
        lock(this)
        {
        if (readerFlag)
        {
          try
          {
          Monitor.Wait(this);
          }
          catch (SynchronizationLockException e)
          {
          file://当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
          Console.WriteLine(e);
          }
          catch (ThreadInterruptedException e)
          {
          file://当线程在等待状态的时候中止
          Console.WriteLine(e);
          }
        }
        cellContents = n;
        Console.WriteLine("Produce: {0}",cellContents);
        readerFlag = true;
        Monitor.Pulse(this); file://通知另外一个线程中正在等待的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); file://生产者向操作对象写入信息
      }
      }

      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; file://一个标志位,如果是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)
        {
        file://当线程因为所处状态的原因而不能执行被请求的操作
        Console.WriteLine(e);
        result = 1;
        }
        catch (ThreadInterruptedException e)
        {
        file://当线程在等待状态的时候中止
        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

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

      四、线程池和定时器——多线程的自动管理
      在多线程的程序中,经常会出现两种情况。一种情况下,应用程序中的线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应;而另外一种情况则是线程平常都处于休眠状态,只是周期性地被唤醒。在.net framework里边,我们使用ThreadPool来对付第一种情况,使用Timer来对付第二种情况。

      ThreadPool类提供一个由系统维护的线程池——可以看作一个线程的容器,该容器需要Windows 2000以上版本的系统支持,因为其中某些方法调用了只有高版本的Windows才有的API函数。你可以使用ThreadPool.QueueUserWorkItem()方法将线程安放在线程池里,该方法的原型如下:
      //将一个线程放进线程池,该线程的Start()方法将调用WaitCallback代理对象代表的函数
      public static bool QueueUserWorkItem(WaitCallback);
      //重载的方法如下,参数object将传递给WaitCallback所代表的方法
      public static bool QueueUserWorkItem(WaitCallback, object);

      要注意的是,ThreadPool类也是一个静态类,你不能也不必要生成它的对象,而且一旦使用该方法在线程池中添加了一个项目,那么该项目将是没有办法取消的。在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠WaitCallback代理对象,而线程的建立、管理、运行等等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题,线程池的优点也就在这里体现出来了,就好像你是公司老板——只需要安排工作,而不必亲自动手。

        下面的例程演示了ThreadPool的用法。首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程,本例中当线程池中所有线程工作都完成以后,ManualResetEvent的对象将被设置为有信号,从而通知主线程继续运行。它有几个重要的方法:Reset(),Set(),WaitOne()。初始化该对象时,用户可以指定其默认的状态(有信号/无信号),在初始化以后,该对象将保持原来的状态不变直到它的Reset()或者Set()方法被调用,Reset()方法将其设置为无信号状态,Set()方法将其设置为有信号状态。WaitOne()方法使当前线程挂起直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。
      using System;
      using System.Collections;
      using System.Threading;

      //这是用来保存信息的数据结构,将作为参数被传递
      public class SomeState
      {
      public int Cookie;
      public SomeState(int iCookie)
      {
        Cookie = iCookie;
      }
      }

      public class Alpha
      {
      public Hashtable HashCount;
      public ManualResetEvent eventX;
      public static int iCount = 0;
      public static int iMaxCount = 0;
      public Alpha(int MaxCount)
      {
        HashCount = new Hashtable(MaxCount);
        iMaxCount = MaxCount;
      }

      file://线程池里的线程将调用Beta()方法
      public void Beta(Object state)
      {
        //输出当前线程的hash编码值和Cookie的值
        Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),
        ((SomeState)state).Cookie);
        Console.WriteLine("HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode());
        lock (HashCount)
        {
        file://如果当前的Hash表中没有当前线程的Hash值,则添加之
        if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
          HashCount.Add (Thread.CurrentThread.GetHashCode(), 0);
        HashCount[Thread.CurrentThread.GetHashCode()] =
    ((int)HashCount[Thread.CurrentThread.GetHashCode()])+1;
        }

        int iX = 2000;
        Thread.Sleep(iX);
        //Interlocked.Increment()操作是一个原子操作,具体请看下面说明
        Interlocked.Increment(ref iCount);
        if (iCount == iMaxCount)
        {
        Console.WriteLine();
        Console.WriteLine("Setting eventX ");
        eventX.Set();
        }
      }
      }

      public class SimplePool
      {
      public static int Main(string[] args)
      {
        Console.WriteLine("Thread Pool Sample:");
        bool W2K = false;
        int MaxCount = 10;//允许线程池中运行最多10个线程
        //新建ManualResetEvent对象并且初始化为无信号状态
        ManualResetEvent eventX = new ManualResetEvent(false);
        Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount);
        Alpha oAlpha = new Alpha(MaxCount); file://创建工作项
        //注意初始化oAlpha对象的eventX属性
        oAlpha.eventX = eventX;
        Console.WriteLine("Queue to Thread Pool 0");
        try
        {
        file://将工作项装入线程池
        file://这里要用到Windows 2000以上版本才有的API,所以可能出现NotSupportException异常
        ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),
        new SomeState(0));
        W2K = true;
        }
        catch (NotSupportedException)
        {
        Console.WriteLine("These API's may fail when called on a non-Windows 2000 system.");
        W2K = false;
        }
        if (W2K)//如果当前系统支持ThreadPool的方法.
        {
        for (int iItem=1;iItem < MaxCount;iItem++)
        {
          //插入队列元素
          Console.WriteLine("Queue to Thread Pool {0}", iItem);
          ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta),new SomeState(iItem));
        }
        Console.WriteLine("Waiting for Thread Pool to drain");
        file://等待事件的完成,即线程调用ManualResetEvent.Set()方法
        eventX.WaitOne(Timeout.Infinite,true);
        file://WaitOne()方法使调用它的线程等待直到eventX.Set()方法被调用
        Console.WriteLine("Thread Pool has been drained (Event fired)");
        Console.WriteLine();
        Console.WriteLine("Load across threads");
        foreach(object o in oAlpha.HashCount.Keys)
        Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]);
        }
        Console.ReadLine();
        return 0;

      }
      }

      程序中有些小地方应该引起我们的注意。SomeState类是一个保存信息的数据结构,在上面的程序中,它作为参数被传递给每一个线程,你很容易就能理解这个,因为你需要把一些有用的信息封装起来提供给线程,而这种方式是非常有效的。程序出现的InterLocked类也是专为多线程程序而存在的,它提供了一些有用的原子操作,所谓原子操作就是在多线程程序中,如果这个线程调用这个操作修改一个变量,那么其他线程就不能修改这个变量了,这跟lock关键字在本质上是一样的。

      我们应该彻底地分析上面的程序,把握住线程池的本质,理解它存在的意义是什么,这样我们才能得心应手地使用它。下面是该程序的输出结果:
      Thread Pool Sample:
      Queuing 10 items to Thread Pool
      Queue to Thread Pool 0
      Queue to Thread Pool 1
      ...
      ...
      Queue to Thread Pool 9
      Waiting for Thread Pool to drain
      98 0 :
      HashCount.Count==0, Thread.CurrentThread.GetHashCode()==98
      100 1 :
      HashCount.Count==1, Thread.CurrentThread.GetHashCode()==100
      98 2 :
      ...
      ...
      Setting eventX
      Thread Pool has been drained (Event fired)
      Load across threads
      101 2
      100 3
      98 4
      102 1

      与ThreadPool类不同,Timer类的作用是设置一个定时器,定时执行用户指定的函数,而这个函数的传递是靠另外一个代理对象TimerCallback,它必须在创建Timer对象时就指定,并且不能更改。定时器启动后,系统将自动建立一个新的线程,并且在这个线程里执行用户指定的函数。下面的语句初始化了一个Timer对象:
      Timer timer = new Timer(timerDelegate, s,1000, 1000);

      第一个参数指定了TimerCallback代理对象;第二个参数的意义跟上面提到的WaitCallback代理对象的一样,作为一个传递数据的对象传递给要调用的方法;第三个参数是延迟时间——计时开始的时刻距现在的时间,单位是毫秒;第四个参数是定时器的时间间隔——计时开始以后,每隔这么长的一段时间,TimerCallback所代表的方法将被调用一次,单位也是毫秒。这句话的意思就是将定时器的延迟时间和时间间隔都设为1秒钟。

      定时器的设置是可以改变的,只要调用Timer.Change()方法,这是一个参数类型重载的方法,一般使用的原型如下:
       public bool Change(long, long);

      下面这段代码将前边设置的定时器修改了一下:
       timer.Change(10000,2000);

      很显然,定时器timer的时间间隔被重新设置为2秒,停止计时10秒后生效。

      下面这段程序演示了Timer类的用法。
      using System;
      using System.Threading;
      class TimerExampleState
      {
      public int counter = 0;
      public Timer tmr;
      }

      class App
      {
      public static void Main()
      {
        TimerExampleState s = new TimerExampleState();

        //创建代理对象TimerCallback,该代理将被定时调用
        TimerCallback timerDelegate = new TimerCallback(CheckStatus);

        //创建一个时间间隔为1s的定时器
        Timer timer = new Timer(timerDelegate, s,1000, 1000);
        s.tmr = timer;

        //主线程停下来等待Timer对象的终止
        while(s.tmr != null)
        Thread.Sleep(0);
        Console.WriteLine("Timer example done.");
        Console.ReadLine();
      }
      file://下面是被定时调用的方法

      static void CheckStatus(Object state)
      {
        TimerExampleState s =(TimerExampleState)state;
        s.counter++;
        Console.WriteLine("{0} Checking Status {1}.",DateTime.Now.TimeOfDay, s.counter);
        if(s.counter == 5)
        {
        file://使用Change方法改变了时间间隔
        (s.tmr).Change(10000,2000);
        Console.WriteLine("changed...");
        }
        if(s.counter == 10)
        {
        Console.WriteLine("disposing of timer...");
        s.tmr.Dispose();
        s.tmr = null;
        }
      }
      }

      程序首先创建了一个定时器,它将在创建1秒之后开始每隔1秒调用一次CheckStatus()方法,当调用5次以后,在CheckStatus()方法中修改了时间间隔为2秒,并且指定在10秒后重新开始。当计数达到10次,调用Timer.Dispose()方法删除了timer对象,主线程于是跳出循环,终止程序。程序执行的结果如下:
    点击在新窗口中浏览此图片

      上面就是对ThreadPool和Timer两个类的简单介绍,充分利用系统提供的功能,可以为我们省去很多时间和精力——特别是对很容易出错的多线程程序。同时我们也可以看到.net Framework强大的内置对象,这些将对我们的编程带来莫大的方便。

      五、互斥对象——更加灵活的同步方式
      有时候你会觉得上面介绍的方法好像不够用,对,我们解决了代码和资源的同步问题,解决了多线程自动化管理和定时触发的问题,但是如何控制多个线程相互之间的联系呢?例如我要到餐厅吃饭,在吃饭之前我先得等待厨师把饭菜做好,之后我开始吃饭,吃完我还得付款,付款方式可以是现金,也可以是信用卡,付款之后我才能离开。分析一下这个过程,我吃饭可以看作是主线程,厨师做饭又是一个线程,服务员用信用卡收款和收现金可以看作另外两个线程,大家可以很清楚地看到其中的关系——我吃饭必须等待厨师做饭,然后等待两个收款线程之中任意一个的完成,然后我吃饭这个线程可以执行离开这个步骤,于是我吃饭才算结束了。事实上,现实中有着比这更复杂的联系,我们怎样才能很好地控制它们而不产生冲突和重复呢?

      这种情况下,我们需要用到互斥对象,即System.Threading命名空间中的Mutex类。大家一定坐过出租车吧,事实上我们可以把Mutex看作一个出租车,那么乘客就是线程了,乘客首先得等车,然后上车,最后下车,当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而线程与Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只有等待。

      下面这个例子使用了Mutex对象来同步四个线程,主线程等待四个线程的结束,而这四个线程的运行又是与两个Mutex对象相关联的。其中还用到AutoResetEvent类的对象,如同上面提到的ManualResetEvent对象一样,大家可以把它简单地理解为一个信号灯,使用AutoResetEvent.Set()方法可以设置它为有信号状态,而使用AutoResetEvent.Reset()方法把它设置为无信号状态。这里用它的有信号状态来表示一个线程的结束。
      // Mutex.cs
      using System;
      using System.Threading;

      public class MutexSample
      {
      static Mutex gM1;
      static Mutex gM2;
      const int ITERS = 100;
      static AutoResetEvent Event1 = new AutoResetEvent(false);
      static AutoResetEvent Event2 = new AutoResetEvent(false);
      static AutoResetEvent Event3 = new AutoResetEvent(false);
      static AutoResetEvent Event4 = new AutoResetEvent(false);

      public static void Main(String[] args)
      {
        Console.WriteLine("Mutex Sample ...");
        //创建一个Mutex对象,并且命名为MyMutex
        gM1 = new Mutex(true,"MyMutex");
        //创建一个未命名的Mutex 对象.
        gM2 = new Mutex(true);
        Console.WriteLine(" - Main Owns gM1 and gM2");

        AutoResetEvent[] evs = new AutoResetEvent[4];
        evs[0] = Event1; file://为后面的线程t1,t2,t3,t4定义AutoResetEvent对象
        evs[1] = Event2;
        evs[2] = Event3;
        evs[3] = Event4;

        MutexSample tm = new MutexSample( );
        Thread t1 = new Thread(new ThreadStart(tm.t1Start));
        Thread t2 = new Thread(new ThreadStart(tm.t2Start));
        Thread t3 = new Thread(new ThreadStart(tm.t3Start));
        Thread t4 = new Thread(new ThreadStart(tm.t4Start));
        t1.Start( );// 使用Mutex.WaitAll()方法等待一个Mutex数组中的对象全部被释放
        t2.Start( );// 使用Mutex.WaitOne()方法等待gM1的释放
        t3.Start( );// 使用Mutex.WaitAny()方法等待一个Mutex数组中任意一个对象被释放
        t4.Start( );// 使用Mutex.WaitOne()方法等待gM2的释放


        Thread.Sleep(2000);
        Console.WriteLine(" - Main releases gM1");
        gM1.ReleaseMutex( ); file://线程t2,t3结束条件满足

        Thread.Sleep(1000);
        Console.WriteLine(" - Main releases gM2");
        gM2.ReleaseMutex( ); file://线程t1,t4结束条件满足

        //等待所有四个线程结束
        WaitHandle.WaitAll(evs);
        Console.WriteLine("... Mutex Sample");
        Console.ReadLine();
      }

      public void t1Start( )
      {
        Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])");
        Mutex[] gMs = new Mutex[2];
        gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAll()方法的参数
        gMs[1] = gM2;
        Mutex.WaitAll(gMs);//等待gM1和gM2都被释放
        Thread.Sleep(2000);
        Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
        Event1.Set( ); file://线程结束,将Event1设置为有信号状态
      }

      public void t2Start( )
      {
        Console.WriteLine("t2Start started, gM1.WaitOne( )");
        gM1.WaitOne( );//等待gM1的释放
        Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
        Event2.Set( );//线程结束,将Event2设置为有信号状态
      }

      public void t3Start( )
      {
        Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");
        Mutex[] gMs = new Mutex[2];
        gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
        gMs[1] = gM2;
        Mutex.WaitAny(gMs);//等待数组中任意一个Mutex对象被释放
        Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
        Event3.Set( );//线程结束,将Event3设置为有信号状态
      }

      public void t4Start( )
      {
        Console.WriteLine("t4Start started, gM2.WaitOne( )");
        gM2.WaitOne( );//等待gM2被释放
        Console.WriteLine("t4Start finished, gM2.WaitOne( )");
        Event4.Set( );//线程结束,将Event4设置为有信号状态
      }
      }

      下面是该程序的执行结果:
    点击在新窗口中浏览此图片
      从执行结果可以很清楚地看到,线程t2,t3的运行是以gM1的释放为条件的,而t4在gM2释放后开始执行,t1则在gM1和gM2都被释放了之后才执行。Main()函数最后,使用WaitHandle等待所有的AutoResetEvent对象的信号,这些对象的信号代表相应线程的结束。

      六、小结

      多线程程序设计是一个庞大的主题,而本文试图在.net Framework环境下,使用最新的C#语言来描述多线程程序的概貌。希望本文能有助于大家理解线程这种概念,理解多线程的用途,理解它的C#实现方法,理解线程将为我们带来的好处和麻烦。C#是一种新的语言,因此它的线程机制也有许多独特的地方,希望大家能通过本文清楚地看到这些,从而可以对线程进行更深入的理解和探索。
    之前已经介绍过Xcache配置方法,经过很多天的使用后,发现Xcache有一个不足,就是会把页面直接缓存起来,造成后台刷新数据,前台不改变等不能及时更新的问题,总来说,Xcache用在有生成静态页面的站上,效果会很好...

    以下来介绍eAccelerator及eAccelerator详细配置

    目录:
    一、eAccelerator介绍
    1、背景
    2、原理
    二、安装和配置
    1、支持平台
    2、系统要求
    3、安装
    4、php.ini文件配置
    5、验证安装
    三、使用eAccelerator开发PHP代码
    1、API文档和接口说明
    2、开发范例
    四、附录和参考资料

    一、eAccelerator介绍

    1、背景
    eAccelerator 是一个免费开源的PHP加速、优化、编译和动态缓存的项目,它可以通过缓存PHP代码编译后的结果来提高PHP脚本的性能,使得一向很复杂和离我们很远的PHP脚本编译问题完全得到解决。通过使用eAccelerator,可以优化你的PHP代码执行速度,降低服务器负载,可以提高PHP应用执行速度最高达10倍。

    eAccelerator 项目诞生于2004年,当时它是作为 Turck MMCache 项目的一个分支提出并投入开发的。 Turck MMCache 由 Dmitry Stogov 开发,是个非常优秀的PHP内存缓存加速系统,如今仍然有很大部分 eAccelerator 的代码应用到该项目中,目前该项目有很长时间没有更新了,对于最新的PHP5.x的支持还未推出。

    2、原理
    eAccelerator 通过把经过编译后的PHP代码缓存到共享内存中,并在用户访问的时候直接调用从而起到高效的加速作用。它的效率非常高,从创建共享内存到查找编译后的代码都在非常短的时间内完成,对于不能缓存到共享内存中的文件和代码,eAccelerator还可以把他们缓存到系统磁盘上。

    eAccelerator 同样还支持PHP代码的编译和解释执行,你可以通过encoder.php脚本来对php代码进行编译达到保护代码的目的,经过编译后的代码必须运行在安装了eAccelerator的环境下。eAccelerator编译后的代码不能被反编译,它不象其他一些编译工具那样可以进行反编译,这将使得代码更加安全和高效。

    二、eAccelerator安装配置

    1、支持平台
    由于aAccelerator提供了大部分基于共享内存的API,所以在*nix的平台上将得到更好的支持,虽然也发布了基于windows平台的binary版本,但我在这里就只提供基于*nix平台的配置和说明,目前可以支持的平台包括Linux, FreeBSD, OpenBSD, Mac OS X, Solaris, AIX en HP-UX。

    2、系统要求
    php4 or php5
    autoconf
    automake
    libtool
    m4
    eAccelerator 只支持使用 mod_php 或者 fastcgi mode 安装的PHP

    3、安装
    先去eAccelerator官方下载最新版的源码包:eaccelerator-0.9.5-beta.tar.bz2

    引用
    #tar -zxvf ./eaccelerator-0.9.5-beta2.tar.bz2
    #cd eaccelerator-0.9.5-beta2
    #export PHP_PREFIX="/usr/local" (把PHP安装目录导入到环境变量,FreeBSD默认是/usr/local)
    #$PHP_PREFIX/bin/phpize
    #./configure --enable-eaccelerator=shared --with-php-config=$PHP_PREFIX/bin/php-config
    #make
    #make install


    4、ini文件配置
    安装完成,下面开始配置php.ini文件,eAccelerator提供了两种配置和调用方式,分别如下。

    安装为 Zend extension 模式:

    引用
    zend_extension="/usr/local/lib/php/20050922/eaccelerator.so"
    eaccelerator.shm_size="16"
    eaccelerator.cache_dir="/tmp/eaccelerator"
    eaccelerator.enable="1"
    eaccelerator.optimizer="1"
    eaccelerator.check_mtime="1"
    eaccelerator.debug="0"
    eaccelerator.log_file = "/var/log/httpd/eaccelerator_log"
    eaccelerator.filter=""
    eaccelerator.shm_max="0"
    eaccelerator.shm_ttl="0"
    eaccelerator.shm_prune_period="0"
    eaccelerator.shm_only="0"
    eaccelerator.compress="1"
    eaccelerator.compress_level="9"


    如果你使用了thread safe模式安装的PHP,你必须使用 “zend_extension_ts” 替换第一行的 “zend_extension”.

    安装为 PHP extension 模式:(这是大部分采用的方式)

    引用
    extension="eaccelerator.so"
    eaccelerator.shm_size="16"
    eaccelerator.cache_dir="/tmp/eaccelerator"
    eaccelerator.enable="1"
    eaccelerator.optimizer="1"
    eaccelerator.check_mtime="1"
    eaccelerator.debug="0"
    eaccelerator.log_file = "/var/log/httpd/eaccelerator_log"
    eaccelerator.filter=""
    eaccelerator.shm_max="0"
    eaccelerator.shm_ttl="0"
    eaccelerator.shm_prune_period="0"
    eaccelerator.shm_only="0"
    eaccelerator.compress="1"
    eaccelerator.compress_level="9"


    有关php.ini文件的详细配置说明,请参照源码目录的README文档或者访问官方文档:ini setting

    完成安装配置后,我们最后要创建缓存目录

    引用
    #mkdir /tmp/eaccelerator
    #chmod 777 /tmp/eaccelerator


    5、验证安装结果
    通过浏览器访问您的phpinfo()页面或者运行 php -i 得到php配置信息,里面如果看到类似下面的信息就表示安装成功了。

    引用
    This program makes use of the Zend Scripting Language Engine:
    Zend Engine v2.1.0, Copyright (c) 1998-2006 Zend Technologies
        with eAccelerator v0.9.5-beta2, Copyright (c) 2004-2006 eAccelerator, by eAccelerator


    我的机器上同时还安装了Zend Optimizer3.0.1,所以看到的信息如下:

    引用
    This program makes use of the Zend Scripting Language Engine:
    Zend Engine v2.1.0, Copyright (c) 1998-2006 Zend Technologies
        with eAccelerator v0.9.5-beta2, Copyright (c) 2004-2006 eAccelerator, by eAccelerator
        with Zend Extension Manager v1.0.10, Copyright (c) 2003-2006, by Zend Technologies
        with Zend Optimizer v3.0.1, Copyright (c) 1998-2006, by Zend Technologies


    如果你打开了eAccelerator的debug选项,可以从日志中看到类似下面的信息

    引用
    #tail /var/log/httpd/eAccelerator_log
    EACCELERATOR hit: "/var/www/toplee.com/blog/index.php"
    EACCELERATOR hit: "/var/www/toplee.com/blog/wp-blog-header.php"
    EACCELERATOR hit: "/var/www/toplee.com/blog/wp-config.php"
    EACCELERATOR hit: "/var/www/toplee.com/blog/wp-settings.php"
    EACCELERATOR hit: "/var/www/toplee.com/blog/wp-content/plugins/wp-cache/wp-cache-phase1.php"
    ...


    以上信息表示文件都得到了缓存和命中。

    至此,我们就完成了全部的安装和配置,好好享受eAccelerator带给你的惊喜吧,根据Michael的测试,效果的确相当的好。

    三、在PHP中可以使用eAccelerator的API开发

    1、API和文档说明:

    eAccelerator提供了便捷便捷而又稳定的本机缓存实现方式,由于大部分代码实现基于共享内存,所以只能在*nix平台中使用,Windows平台Michael就暂时不知道何时有这方面的支持了。
    eAccelerator提供如下的API接口和文件:(下述文件均在源码包的doc/php/目录下)

    文件列表:

    引用
    cache.php
    dasm.php
    encoder.php
    info.php
    loader.php
    session.php
    shared_memory.php


    接口列表:

    引用
    array eaccelerator_cached_scripts ()
    void eaccelerator_cache_output (string $key, string $eval_code, [int $ttl = 0])
    void eaccelerator_cache_page (string $key, [int $ttl = 0])
    void eaccelerator_cache_result (string $key, string $code, [int $ttl = 0])
    void eaccelerator_caching (boolean $flag)
    void eaccelerator_clean ()
    void eaccelerator_clear ()
    array eaccelerator_dasm_file (mixed $filename)
    mixed eaccelerator_encode (mixed $src, [mixed $prefix = ''], [string $pre_content = ''], [string $post_content = ''])  
    void eaccelerator_gc ()
    mixed eaccelerator_get (string $key)  
    array eaccelerator_info ()
    array eaccelerator_list_keys ()
    void eaccelerator_load ()
    boolean eaccelerator_lock (string $key)
    void eaccelerator_optimizer (boolean $flag)  
    void eaccelerator_purge ()
    boolean eaccelerator_put (string $key, mixed $value, [int $ttl = 0])
    array eaccelerator_removed_scripts ()
    boolean eaccelerator_rm (string $key)
    void eaccelerator_rm_page (string $key)  
    boolean eaccelerator_set_session_handlers ()
    boolean eaccelerator_unlock (string $key)


    有关上述文档详细说明请参考官方文档:API Documents

    下面有部分网友翻译后的接口说明:

    引用
    eaccelerator_put($key, $value, $ttl=0)
      将 $value 以 $key 为键名存进缓存(php4下支持对像类型,看源码好像zend2里不支持了),$ttl 是这个缓存的生命周期,单位是秒,省略该参数或指定为 0 表示不限时,直到服务器重启清空为止。

    eaccelerator_get($key)
      根据 $key 从缓存中返回相应的 eaccelerator_put() 存进去的数据,如果这项缓存已经过期或不存在那么返回值是 NULL

    eaccelerator_rm($key)
      根据 $key 移除缓存

    eaccelerator_gc()
      移除清理所有已过期的 key

    eaccelerator_lock($key)
      为 $key 加上锁定操作,以保证多进程多线程操作时数据的同步。需要调用 eaccelerator_unlock($key) 来释放这个锁或等待程序请求结束时自动释放这个锁。
      例如:
          eaccelerator_lock("count");
        eaccelerator_put("count",eaccelerator_get("count")+1));
      ?>

    eaccelerator_unlock($key)
      根据 $key 释放锁

    eaccelerator_cache_output($key, $eval_code, $ttl=0)
      将 $eval_code 代码的输出缓存 $ttl 秒,($ttl参数同 eacclerator_put)
      例如:
      

    eaccelerator_cache_result($key, $eval_code, $ttl=0)
      将 $eval_code 代码的执行结果缓存 $ttl 秒,($ttl参数同 eacclerator_put),类似 cache_output
      例如:
      

    eaccelerator_cache_page($key, $ttl=0)
      将当前整页缓存 $ttl 秒。
      例如:
          eaccelerator_cache_page($_SERVER['PHP_SELF'].'?GET='.serialize($_GET),30);
        echo time();
        phpinfo();
      ?>

    eaccelerator_rm_page($key)
      删除由  eaccelerator_cache_page() 执行的缓存,参数也是 $key


    2、PHP代码中使用eAccelerator加速

    下面有一个测试的代码,你可以测试一下eAccelerator强大的威力:(该代码在 cli 模式下可能无效)

    引用
    class test_cache {
      var $pro = 'hello';

      function test_cache() {
        echo "Object Created!
    \n";
      }
      function func() {
        echo ', the world!';
      }
      function now($t) {
        echo date('Y-m-d H:i:s', $t);
      }
    }

    $tt = eaccelerator_get("test_tt");
    if (!$tt)
    {
      $tt = new test_cache;
      eaccelerator_put("test_tt", $tt);
      echo "no cached!
    \n";
    }
    else {
      echo "cached
    \n";
    }

    echo $tt->pro;
    $tt->func();
    $tt->now(time() + 86400);
    ?>


    另外,据说在著名的vBulletin 3.60Beta版里面已经集成了对eAccelerator的支持,下面是一段来自vBulletin里面的代码

    引用
    // #############################################################################
    // eAccelerator

    /**
    * Class for fetching and initializing the vBulletin datastore from eAccelerator
    *
    * @package    vBulletin
    * @version    $Revision: 0.1 $
    * @date        $Date: 2005/06/12 13:14:18 $
    */
    class vB_Datastore_eAccelerator extends vB_Datastore
    {
        /**
        * Fetches the contents of the datastore from eAccelerator
        *
        * @param    array    Array of items to fetch from the datastore
        *
        * @return    void
        */
        function fetch($itemarray)
        {
            if (!function_exists('eaccelerator_get'))
            {
                trigger_error("eAccelerator not installed", E_USER_ERROR);
            }

            foreach ($this->defaultitems AS $item)
            {
                $this->do_fetch($item);
            }

            if (is_array($itemarray))
            {
                foreach ($itemarray AS $item)
                {
                    $this->do_fetch($item);
                }
            }

            $this->check_options();

            // set the version number variable
            $this->registry->versionnumber =& $this->registry->options['templateversion'];
        }

        /**
        * Fetches the data from shared memory and detects errors
        *
        * @param    string    title of the datastore item
        *
        * @return    void
        */
        function do_fetch($title)
        {
            $data = eaccelerator_get($title);
            if ($data === null)
            { // appears its not there, lets grab the data, lock the shared memory and put it in
                $data = '';
                $dataitem = $this->dbobject->query_first("
                    SELECT title, data FROM " . TABLE_PREFIX . "datastore
                    WHERE title = '" . $this->dbobject->escape_string($title) ."'
                ");
                if (!empty($dataitem['title']))
                {
                    $data =& $dataitem['data'];
                    $this->build($dataitem['title'], $dataitem['data']);
                }
            }
            $this->register($title, $data);
        }




        /**
        * Updates the appropriate cache file
        *
        * @param    string    title of the datastore item
        *
        * @return    void
        */
        function build($title, $data)
        {
            if (!function_exists('eaccelerator_put'))
            {
                trigger_error("eAccelerator not installed", E_USER_ERROR);
            }
            eaccelerator_lock($title);
            eaccelerator_put($title, $data);
            eaccelerator_unlock($title);
        }
    }


    四、附录和参考资料
    eAccelerator 官方网站 :http://eaccelerator.net
    Websites using eAccelerator ?

    http://www.advfn.com/ (2006/03/03) Advanced Financial Network, serves over 7 million page hits per day.
    http://www.domaincity.co.uk/ (2005/01/29) Andrew Hutchings - Linux Guru - Just a quick message to say we are now using eaccelerator as a replacement for mmcache on 2 of our 3 node round robin clusters, 2.5million hits per day. We will be doubling this in the next few week, as well as implementing new clusters.
    http://www.ets-online.de/ (2005/02/07) Denny Reeh - System Developer - The e/t/s didactic media provides an elearning server (Distance Learning System) written in PHP. We are hosting the systems for our customers mainly by ourself. So we have 5 live-servers (freebsd, apache2, php4, eaccelerator) with 5.5 million hits per month. First without eAccelerator we should have installed more servers for good performance and secondly we improve the customer convenience by quartering the medium response time of a script.
    http://www.gorgoyle.com/ (2005/08/03) Gorgoyle.com is Yet Another Debian Personal Server with just a few lost Internet wanderers passing by, powered by a glorious Celeron 433 that needed some fresh air… With eAccelerator the result is quite impressive: users say the server speed is now 10000% higher (no joke!) So thank you all and keep up the good work!!! Server: eAccelerator 0.9.5 beta 1 Apache/2.0.55 (Debian) mod_python/3.1.3 Python/2.3.5 PHP/4.4.2-1 mod_ssl/2.0.55 OpenSSL/0.9.8a mod_perl/2.0.2
    http://www.mafia-inc.de/ (2005/05/24) The Mafia-Incorporated is a succesfull Browser game with around 2.500.000 page impressions each day and over 5000 users. Server: PHP Version 4.3.10-9, eAccelerator 0.9.2a, Debian , Apache 2.0.53
    http://www.mondespersistants.com/ (2005/01/07) An other game oriented french website with 6 000 000 pageloads monthly. http://guildes.mondespersistants.com is a full PHP5 oo development. Theses websites are running under freebsd / apache 1.3 / php 5.0.3 / eaccelerator 0.9.2
    http://www.moviemaze.de/ (2005/12/09) Markus Ostertag - Movie Maze is a large german online-magazine with trailer, wallpaper, reviews, news and much more. Currently we use three servers (Suse & Debian with Apache 2, PHP4) with eAccelerator and provide more than 3TB data to ~1 Mio. users each month.
    http://www.rtvslo.si (2005/01/04) The Slovenian National RTV Station and its news/misc portal with forums. Over 3500 users and more than 37000 forum messages in a year. Two round-robin servers for the masses, and an administrator server. 6 000 000 pageloads monthly.
    http://www.shroomery.org/ (2006/03/28) A site about mushrooms and mycology, serving 20,000,000 pages per month. Web and database server are identical dual 64-bit Xeons with 4GB and SCSI RAID5 running Gentoo. eAccelerator help keep our page generation time under .1 second!

    http://www.sourceforge.net/ (2005/04/25) SourceForge.net is the world’s largest Open Source software development web site, hosting more than 100,000 projects and over 1,000,000 registered users with a centralized resource for managing projects, issues, communications, and code. SourceForge.net has the largest repository of Open Source code and applications available on the Internet, and hosts more Open Source development products than any other site or network worldwide. SourceForge.net provides a wide variety of services to projects we host, and to the Open Source community. eAccelerator is used on the webserver cluster for their frontpage.
    Companies offering hosting with eAccelerator enabled ?

    http://lylix.net/ (2006/04/29) Offering webhosting w/ Apache/2.0.55, PHP 5.0.5, and eAccelerator 0.9.5 beta1 on Gentoo Linux servers. Also a provider of quality Virtual Private Server (VPS) hosting, including 32 and 64-bit platforms. Most VPS images have a pre-installed LAMP environment w/ eAccelerator installed and configured.
    http://www.ulyssis.org A student organisation which provides shell and internet services for students at very cheap prices.

    本文为Michael原创和整理,转载请保留出处:http://www.toplee.com/blog/?p=100
    此文章首发于牛C网,如需转载,请保留此行...

    一.准备工作

    windows 2003,自己买吧...

    安装IIS 6.0:
    安装系统后在"控制面板"->"添加或删除程序"->"添加/删除Windows组件"->双击"应用程序服务器"->然后选中"Internet信息服务(IIS)"->确定->安装完毕...

    下载PHP:http://cn2.php.net/get/php-5.2.5-Win32.zip/from/this/mirror

    下载MySQL:http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-5.0.51a-win32.zip/from/pick#mirrors
    根据自己的速度,随便点击下面一个国家后面的HTTP/FTP之类的链接进行下载...

    下载Zend Optimizer:http://www.zend.com/en/products/guard/downloads
    自行选择版本下载,我下的是3.3.3版...

    下载Xcache:http://xcache.lighttpd.net/wiki/Release-1.2.2

    很多人可能不知道Xcache是什么,下面作个简单介绍:

    XCache是一种新的php缓存器,经过本人测试,在Windows下效果比同类软件强很多.

    实际测试效果如下(非科学方法):

    原网页平均执行时间: 0.13 秒
    加速后网页平均执行时间: 0.03秒
    原CPU占用率: 15% - 60%
    加速后CPU占用率: 3% - 30%


    由上数据可以看出,使用这个加速器可以很大程度的降低CPU占用(但会造成更多的内存占用),并使网页执行时间缩短.

    由于其他同类软件对Windows的支持不够好,所以选用XCache是Windows主机的不二之选.

    怎样提高网站的PR值 雨

    Category : Seo技术 | Post on 2008/02/25 17:23 by gdgzboy | Comments:0
        无论是对普通网络冲浪者还是网站管理员来说,Google都是目前世界范围内最受欢迎的搜索引擎。它每天处理的搜索请求高达1.5亿次,几乎占全球所有搜索量的1/3。网络冲浪者对Google情有独钟,是由于Google所提供的快速搜索速度及高命中率搜索结果。这些都是基于Google的复杂文本匹配运算法则及其搜索程序所使用的PageRank系统(网页级别技术)。

    下面我们将向大家介绍Google的Pagerank系统。

        Google之所以受网站管理员和Internet媒体服务公司的欢迎,是由于它并非只使用关键词或代理搜索技术,而是将自身建立在高级的网页级别技术基础之上。别的搜索引擎提供给搜索者的是多种渠道得来的一个粗略的搜索结果,而Google提供给它的搜索者的则是它自己产生的高度精确的搜索结果。这就是为什么网站管理员会千方百计去提高自己网站在Google的排名。

        Google大受青睐的另一个原因就是它的网站索引速度。向Google提交你的网站直到为Google收录,一般只需两个星期。如果你的网站已经为Google收录,那么通常Google会每月一次遍历和更新(重新索引)你的网站信息。不过对于那些PR值(Pagerank)较高的网站,Google索引周期会相应的短一些。

        Google的索引/重新索引周期比大多数搜索引擎要短。这就允许网站管理员可以对网站的页面属性进行编辑修改,如网页标题、头几行文字内容、大字标题、关键字分布,当然了还有外部链接的数量。然后他们很快就可以发现对网页所做的这些更改是否成功。

        正因为Google如此受欢迎,你有必要知道Google的搜索引擎是如何工作的。如果不知道它是怎样决定你的排名,那么那些只是稍微熟悉Google排名运算法则的站点都会比你的排名位置要靠前。
    Tags: , , , , ,

    ActionScript处理异步事件(三) 雨

    Category : Flash技术 | Post on 2008/02/24 17:09 by gdgzboy | Comments:0
    原文地址:http://kuwamoto.org/2006/05/19/dealing-with-asynchronous-events-part-3/
    作者:Sho Kuwamoto
    译者:Dreamer

    我想要谈论一下Flex 2中对RPC呼叫机制的一些改进。如果不是Matt Chotin友好地告诉我Flex 2中的RPC呼叫已经正式改为返回AsyncToken类型的对象,我可能永远都不会知道,这再一次证明了关于Flex他比我知道的多。

    使用 AsyncToken 和 Responders

    为了使用as-is机制,你要象这样做:

    你会发现它与上一篇文章中的第一个例子非常相似。 最主要的区别就是现在我们 在framework里而不是在你的代码中实现处理器(handler)。

    C#开发的Mofile采集程序 多云

    Category : DotNet技术 | Post on 2008/02/23 14:32 by gdgzboy | Comments:8
    此文章首发于牛C网,如需转载,请保留此行...

    首先声明,开发此程序只为了学习.研究.交流之用,非法使用造成的后果,与作者无关.

    先上截图:
    点击在新窗口中浏览此图片
    点击在新窗口中浏览此图片

    程序使用的是.NET(c#)开发,因此在使用前,请确认安装了Microsoft .NET Framework 2.0,下载地址为:
    http://www.microsoft.com/downloads/details.aspx?familyid=0856EACB-4362-4B0D-8EDD-AAB15C5E04F5&displaylang=zh-cn

    程序使用的数据库为MS SQL SERVER 2005,请确认机器上有安装MS SQL 2005,然后下载以下压缩包,执行SQL脚本...

    Tags: , ,
    异步读取输出信息的代码...

    在很多情况下我们需要将指定的数据库中的所有表都列出来。在使用c#进行软件开发时,我们有哪些方法可是实现这个目的呢?本人对此进行概要的总结,有以下6中方式可以实现这个目的。

    1、sqldmo
    SQLDMO是操作SQLServer的理想的方式,如果您的数据库是SQLServer就可以考虑使用这种方式。在C#中使用SQLDMO需要添加SQLDMO的引用,然后在当前的文件中using SQLDMO;即可以使用SQLDMO。SQLDMO的对象模型大家可以在SQLServer的帮助中获得。

    .net和Flash交互的在线视频系统 不指定

    Category : Flash技术 | Post on 2008/02/22 22:19 by gdgzboy | Comments:0
    demo地址:http://bb.feel2u.com

    好象是大半年前做的吧...效果还不错...

    有空会把代码整理好放出来的,敬请关注...
    FLEX例子http://www.onflex.org/flexapps/components/CustomPreloader/

    代码详解

    这几天由于领导安排,所以写了一个EXCEL的多工作表(同一工作簿)搜索宏,除了能在原表高亮显示匹配项外,还自动把匹配的结果复制到一个新的工作表中,并在复制结果时加入指向源表的链接,非常方便.
    Tags: , , ,
    分页: 1/1 第一页 1 最后页 [ 显示模式: 摘要 | 列表 ]