• 2009-03-11

    关于每一个数据库表都应该有一个单一的字段作为主键的讨论

    Views: 15316 | 3 Comments

    2010年5月6日更新: 只有真正懂得了这个道理的人, 才算真正理解了关系数据库. 如何才算懂得了这个道理? - 即使你有一百个理由要用关联主键, 你也能找到这唯一的一个理由放弃, 改而使用单一字段做主键.

    ------

    在数据库设计中, 每一个表都应该有一个字段作为主键. 这个字段一般是自增整数字段, 或者某些数据库支持的自动产生不重复字符串的字段, 也可以是程序自己产生的唯一标识. 总之, 每一个数据库表都应该有一个单一的字段作为主键.

    使用单一的字段作为表的主键, 有许多优点. 最重要的一条是可以通过一个原子属性(如整数, 字符串)来标识一行记录. 一旦可以方便地标识一行记录, 那么数据的查询, 更新, 删除也都非常简单.

    如果一个表没有主键, 那么必须通过一行记录本身才能标识一行记录. 也就是, 你必须知道一行记录的每一个字段的值, 才能在 SQL 语句中操作这条记录.
    Continue reading »

    Posted by ideawu at 2009-03-11 20:03:07
  • 2009-02-20

    C#环形缓冲

    Views: 10575 | No Comments
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading;
    
    /**
     * 缓冲区的可能状态:
     * <code>
     * (1)
     * ----====== data ======-----rspace----
     *     |                 |             |
     *     rd_nxt            wr_nxt        capacity-1
     * (2)
     * ==ldata==-------------==== rdata ====
     *          |            |             |
     *          wr_nxt       rd_nxt        capacity-1
     * (3)
     * ===ldata=============rdata===========(full of data)
     *             |
     *             wr_nxt(rd_nxt)
     * (4)
     * -------------------------------------(empty)
     *           |
     *           wr_nxt(rd_nxt)
     * </code>
     */
    
    /// <summary>
    /// 使用字节数组来实现的缓冲区. 该缓冲区把该数组看作是一个环,
    /// 支持在一块固定的数组上的无限次读和写, 数组的大小不会自动变化.
    /// ideawu
    /// </summary>
    /// <typeparam name="T">所缓冲的数据类型.</typeparam>
    public class ArrayBuffer<T>
    {
    	/// <summary>
    	/// 默认大小.
    	/// </summary>
    	private const int DFLT_SIZE = 512 * 1024;
    
    	/// <summary>
    	/// 缓冲区还能容纳的元素数目.
    	/// </summary>
    	private int space = 0;
    
    	/// <summary>
    	/// 缓冲区中的数据元素数目.
    	/// </summary>
    	private int available = 0;
    
    	/// <summary>
    	/// 缓冲区的容量.
    	/// </summary>
    	private int capacity = DFLT_SIZE;
    	// 注意 capacity 和 buf.Length 可以不相同, 前者小于或者等于后者.
    
    	/// <summary>
    	/// 下一次要将数据写入缓冲区的开始下标.
    	/// </summary>
    	private int wr_nxt = 0;
    
    	/// <summary>
    	/// 下一次读取接收缓冲区的开始下标.
    	/// </summary>
    	private int rd_nxt = 0;
    
    	private int readTimeout = -1;
    
    	private int writeTimeout = -1;
    
    	private Semaphore writeSemaphore = new Semaphore(1, 1);
    
    	/// <summary>
    	/// 缓冲区所使用的数组.
    	/// </summary>
    	private T[] dataBuf;
    
    	private object bufLock = new object();
    
    	/// <summary>
    	/// 如果当前缓冲区中有数据可读, 它将会被设置.
    	/// </summary>
    	private Semaphore readSemaphore = new Semaphore(0, 1);
    
    	/// <summary>
    	/// 创建一个具体默认容量的缓冲区.
    	/// </summary>
    	public ArrayBuffer()
    		: this(DFLT_SIZE) {
    	}
    
    	/// <summary>
    	/// 创建一个指定容量的缓冲区.
    	/// </summary>
    	/// <param name="capacity">缓冲区的容量.</param>
    	public ArrayBuffer(int capacity)
    		: this(new T[capacity]) {
    	}
    
    	/// <summary>
    	/// 使用指定的数组来创建一个缓冲区.
    	/// </summary>
    	/// <param name="buf">缓冲区将要使用的数组.</param>
    	public ArrayBuffer(T[] buf)
    		: this(buf, 0, 0) {
    	}
    
    	/// <summary>
    	/// 使用指定的数组来创建一个缓冲区, 且该数组已经包含数据.
    	/// </summary>
    	/// <param name="buf">缓冲区将要使用的数组.</param>
    	/// <param name="offset">数据在数组中的偏移.</param>
    	/// <param name="size">数据的字节数.</param>
    	public ArrayBuffer(T[] buf, int offset, int size) {
    		this.dataBuf = buf;
    		capacity = buf.Length;
    		available = size;
    		space = capacity - available;
    		rd_nxt = offset;
    		wr_nxt = offset + size;
    	}
    
    	/// <summary>
    	/// 缓冲区还能容纳的元素数目.
    	/// </summary>
    	public int Space {
    		get {
    			return space;
    		}
    	}
    
    	/// <summary>
    	/// 缓冲区中可供读取的数据的元素数目
    	/// </summary>
    	public int Available {
    		get {
    			return available;
    		}
    	}
    
    	/// <summary>
    	/// get, set 接收缓冲区的大小(元素数目). 默认值为 512K.
    	/// Capacity 不能设置为小于 Available 的值(实现会忽略这样的值).
    	/// </summary>
    	public int Capacity {
    		get {
    			return capacity;
    		}
    		set {
    			lock (bufLock) {
    				if (value < available || value == 0) {
    					return;
    					//throw new ApplicationException("Capacity must be larger than Available.");
    				}
    				if (value == capacity) {
    					return;
    				}
    				if (value > capacity && space ==0) {
    					// 可写空间变为非空, 释放可写信号.
    					writeSemaphore.Release();
    				}
    
    				T[] buf = new T[value];
    				if (available > 0) {
    					available = ReadData(buf, 0, buf.Length);
    					// 下面的用法是错误的!
    					//available = Read(buf, 0, buf.Length);
    				}
    				dataBuf = buf;
    				capacity = value;
    				space = capacity - available;
    				rd_nxt = 0;
    				// 当容量缩小时, 可能导致变化后可写空间为0, 这时wr_nxt=0.
    				wr_nxt = (space == 0) ? 0 : available;
    			}
    		}
    	}
    
    	/// <summary>
    	/// Read 方法的超时时间(单位毫秒). 默认为 -1, 表示无限长.
    	/// </summary>
    	public int ReadTimeout {
    		get {
    			return readTimeout;
    		}
    		set {
    			readTimeout = value;
    		}
    	}
    
    	/// <summary>
    	/// Write 方法的超时时间(单位毫秒). 默认为 -1, 表示无限长.
    	/// </summary>
    	public int WriteTimeout {
    		get {
    			return writeTimeout;
    		}
    		set {
    			writeTimeout = value;
    		}
    	}
    
    	/// <summary>
    	/// 清空本缓冲区.
    	/// </summary>
    	public void Clear() {
    		lock (bufLock) {
    			available = 0;
    			space = capacity;
    			rd_nxt = 0;
    			wr_nxt = 0;
    		}
    	}
    
    	/*
    	/// <summary>
    	/// 将读指针向前移动 num 个单元. 如果 num 大于 Avalable,
    	/// 将抛出异常.
    	/// </summary>
    	/// <param name="num">读指针要向前的单元个数.</param>
    	/// <exception cref="ApplicationException">num 大于 Avalable.</exception>
    	public void Seek(int num) {
    	}
    	*/
    
    	/// <summary>
    	/// 未实现.
    	/// </summary>
    	/// <returns></returns>
    	public T ReadOne() {
    		throw new Exception("Not supported.");
    	}
    
    	/// <summary>
    	/// 从缓冲区中读取数据. 读取的字节数一定是 buf.Length 和 Available 的较小者.
    	/// </summary>
    	/// <param name="buf">存储接收到的数据的缓冲区.</param>
    	/// <returns>已经读取的字节数. 一定是 size 和 Available 的较小者.</returns>
    	public int Read(T[] buf) {
    		return Read(buf, 0, buf.Length);
    	}
    
    	/// <summary>
    	/// 从缓冲区中读取数据. 读取的字节数一定是 size 和 Available 的较小者.
    	/// 本方法是线程安全的.
    	/// </summary>
    	/// <param name="buf">存储接收到的数据的缓冲区.</param>
    	/// <param name="offset">buf 中存储所接收数据的位置.</param>
    	/// <param name="size">要读取的字节数.</param>
    	/// <returns>已经读取的字节数. 一定是 size 和 Available 的较小者.</returns>
    	public int Read(T[] buf, int offset, int size) {
    		if (!readSemaphore.WaitOne(readTimeout, false)) {
    			throw new ApplicationException("Read timeout.");
    		}
    
    		lock (bufLock) {
    			int nread = ReadData(buf, offset, size);
    			if (space == 0) {
    				// 释放可写信号.
    				writeSemaphore.Release();
    			}
    			space += nread;
    			available -= nread;
    			if (available > 0) {
    				// 释放一个信号, 以便下一次再读.
    				readSemaphore.Release();
    			}
    			return nread;
    		}
    	}
    
    	/// <summary>
    	/// 把本缓冲区的数据复制指定的数组中, 并移动读指针.
    	/// </summary>
    	private int ReadData(T[] buf, int offset, int size) {
    		int nread = (available >= size) ? size : available;
    		// 当 rd_nxt 在 wr_nxt 的左边时, 缓冲的右边包含的网络字节数.
    		int rdata = capacity - rd_nxt;
    		if (rd_nxt < wr_nxt || rdata >= nread/*隐含rd_nxt >= wr_nxt*/) {
    			Array.Copy(dataBuf, rd_nxt, buf, offset, nread);
    			rd_nxt += nread;
    		} else {
    			// 两次拷贝.
    			Array.Copy(dataBuf, rd_nxt, buf, offset, rdata);
    			rd_nxt = nread - rdata;
    			Array.Copy(dataBuf, 0, buf, offset + rdata, rd_nxt);
    		}
    		return nread;
    	}
    
    	/// <summary>
    	/// 写入数据到缓冲区.
    	/// </summary>
    	/// <param name="buf">要写入的数据的缓冲区.</param>
    	public void Write(byte[] buf) {
    		Write(buf, 0, buf.Length);
    	}
    
    	/// <summary>
    	/// 写入数据到缓冲区. 注意: 本方法不是线程安全的.
    	/// </summary>
    	/// <param name="buf">要写入的数据的缓冲区.</param>
    	/// <param name="offset">数据缓冲区中要写入数据的起始位置.</param>
    	/// <param name="size">要写入的字节数.</param>
    	/// <exception cref="ApplicationException">如果空间不足, 会抛出异常.</exception>
    	public void Write(byte[] buf, int offset, int size) {
    		int n_left = size;
    		int n_offset = offset;
    		int nwrite;
    		int rspace;
    		while (n_left > 0) {
    			// 这样的超时控制并不准确!
    			if (!writeSemaphore.WaitOne(writeTimeout, false)) {
    				throw new ApplicationException("Write timeout.");
    			}
    
    			lock (bufLock) {
    				nwrite = (space >= n_left) ? n_left : space;
    				// 当 rd_nxt 在 wr_nxt 的左边时, 缓冲的右边可以放置的网络字节数.
    				rspace = capacity - wr_nxt;
    				if (wr_nxt < rd_nxt || rspace >= nwrite/*隐含wr_nxt >= rd_nxt*/) {
    					Array.Copy(buf, n_offset, dataBuf, wr_nxt, nwrite);
    					wr_nxt += nwrite;
    					if (wr_nxt == capacity) {
    						wr_nxt = 0;
    					}
    				} else {
    					// 两次拷贝.
    					Array.Copy(buf, n_offset, dataBuf, wr_nxt, rspace);
    					wr_nxt = nwrite - rspace; // 是调用下一句之后的 wr_nxt值.
    					Array.Copy(buf, n_offset + rspace, dataBuf, 0, wr_nxt);
    				}
    				if (available == 0) {
    					readSemaphore.Release();
    				}
    				space -= nwrite;
    				available += nwrite;
    				if (space > 0) {
    					// 释放可写信号.
    					writeSemaphore.Release();
    				}
    
    				n_offset += nwrite;
    				n_left -= nwrite;
    			}
    		} // end while
    
    		/* 不需要 WriteTimeout 的版本.
    		// 和 Read 是对称的.
    		lock (bufLock) {
    			if (space < size) {
    				// TBD: 是否实现写超时机制?
    				throw new ApplicationException("Not enough space.");
    			}
    
    			// 当 wr_nxt 在 rd_nxt 的左边时, 缓冲的右边可以放置的网络字节数.
    			int rspace = capacity - wr_nxt;
    			if (wr_nxt < rd_nxt || rspace >= size) {
    				Array.Copy(buf, offset, dataBuf, wr_nxt, size);
    				wr_nxt += size;
    			} else {
    				// 两次拷贝.
    				Array.Copy(buf, offset, dataBuf, wr_nxt, rspace);
    				wr_nxt = size - rspace;
    				Array.Copy(buf, offset + rspace, dataBuf, 0, wr_nxt);
    			}
    			if (available == 0) {
    				readSemaphore.Release();
    			}
    			space -= size;
    			available += size;
    		}
    		*/
    	}
    }
    
    Posted by ideawu at 2009-02-20 12:36:12
  • 2008-12-04

    Mesh: 对象的多对多关系

    Views: 8876 | No Comments

    在数据库设计中, 有三种映射关系: 一对一, 一对多, 多对多.

    一对一关系几乎没有任何用处, 任何一对一关系都是因为技术的限制, 而不是业务的本质.

    一对多(或者多对一)关系有很大的用处, 因为它能表示树形结构. 不过, 很多重要的情况又不能用树来表示.

    多对多关系表示网状关系, 这才是现实事物的反映. 但是, 多对多比一对多复杂了一个数量级, 如果能使用一对多, 会使事情变得简单. 同理, 一对多比一对一复杂了一个数量级.

    在关系数据库设计中, 一对多使用2个表, 多对多使用3个表. 所以, 关系数据库不能很好地处理多对多关系. 我们需要一种新型的十分适合多对多关系的数据存储和操作工具, 也就是说, 我们需要一种网式数据库.

    Posted by ideawu at 2008-12-04 17:44:08
  • 2008-05-21

    一种有趣的编程模型

    Views: 10298 | 1 Comment

    假设我们从某处接收命令, 一次只能接收一个. 如果命令为 1, 我们只要祈祷, 就可以得到金钱. 如果命令为 2, 那么我们必须到外面 ATM 机取钱, 然后放入钱包.

    这个模型可以用下面的计算机伪代码描述:

    while(cmd = read_cmd()){
        switch(cmd){
            case 1:
                m = pray();
                wallet.put(m);
                break;
            case 2:
                m = get_money_from_atm();
                wallet.put(m);
                break;
        }
    }
    

    从直觉可以知道, get_money_from_atm() 耗时可能达到 1 个小时, 但是 pray() 只需要几秒. 上面的程序是线性的, 所以 get_money_from_atm() 会影响我们接收新的命令, 从而影响我们的钱包快速的增加. 这是一个严重的问题! 下面将讨论如何解决这个问题.

    我们可以委托其他人帮我们去 ATM 机取钱. 所以,

    m = get_money_from_atm();
    wallet.put(m);
    

    可以改为

    delegate(){
        m = get_money_from_atm();
        wallet.put(m);
    }
    

    delegate 代码段中的代码会立即被加入到异步执行队列中(在计算机中常常由其它的线程处理这个队列), 所以我们又可以去接收命令了.

    问题是, wallet.put() 不是线程安全的, 所以, 调用它时必须加锁. 所以代码应该是

    delegate(){
        m = get_money_from_atm();
        lock(wallet){
            wallet.put(m);
        }
    }
    
    wallet.put(m);
    

    也要修改为

    lock(wallet){
        wallet.put(m);
    }
    

    最终的代码是

    while(cmd = read_cmd()){
        switch(cmd){
            case 1:
                m = pray();
                lock(wallet){
                    wallet.put(m);
                }
                break;
            case 2:
                // 这段代码交给另一个线程执行, 所以它几乎不消耗当前线程的时间.
                delegate(){
                    m = get_money_from_atm();
                    lock(wallet){
                        wallet.put(m);
                    }
                }
                break;
        }
    }
    

    这里还有很大的改进空间, case 1 情况比 case 2 出现的机率大得多, 很多时候并没有其它的线程争用 wallet, 但我们还是对它进行加锁, 这些加锁都是无谓的. 我想, 能不能达到下面的效果:

    while(cmd = read_cmd()){
        switch(cmd){
            case 1:
                m = pray();
                wallet.put(m);
                break;
            case 2:
                delegate(){
                    // 将本代码块中的代码委托给其它线程执行.
                    m = get_money_from_atm();
                }then{
                    // 这里的代码会在委托提交后执行.
                    continue;
                }onexit{
                    // 一旦委托的代码执行完毕, 会跳到这里继续执行.
                    wallet.put(m);
                }// 这段几乎不会消耗当前线程的时间.
                break;
        }
    }
    

    委托调用立即返回, 但是, 一旦委托过程处理完毕, 代码又被插入原来的地方继续执行. 也就是说, 一旦 get_money_from_atm() 在另一个线程中执行, 便会产生一个中断, 并执行 onexit 代码段. 当然, 这个中断的级别不能高到中断其它代码的执行, 如 pray().

    有一个可以改进的地方, 就是我们不希望委托的数量过多, 所以为 delegate 增加一个参数, delegate(maxwait=2), 表示如果有 2 个执行中的委托, 便阻塞当前的线程.

    Posted by ideawu at 2008-05-21 19:02:27
  • 2008-01-10

    TCP/IP 指数增长和线性增长的编程实现

    Views: 15975 | 1 Comment

    在 TCP 的慢启动和拥塞控制中, 当发生拥塞时, TCP 进行慢启动, 当慢启动进行到一定阀值后进行拥塞避免. 正如 "TCP/IP 协议详解" 中提到的, "慢启动" 这个术语并不准确, 拥塞窗口在慢启动期间以指数速率增长. 在拥塞避免算法进行时, 拥塞窗口以线性速率增长, 每次加 1.

    这两个算法可以用编程语言来实现:

    using System;
    
    class Test
    {
        static int cwnd1 = 1;
        static int cwnd2 = 1;
        static int ssthresh = 64;
    
        public static void Main(){
            int step = 20;
            for(int i=0; i<step; i++){
                grow1();
                grow2();
                Console.WriteLine("{0,8}\t{1,8}", cwnd1, cwnd2);
            }
        }
    
        // 完全指数增长.
        static void grow1(){
            int num = cwnd1;
            for(int n=0; n<num; n++){
                cwnd1 += 1;
            }
        }
    
        // 先指数增长, 然后加性增长.
        static void grow2(){
            int wnd = cwnd2;
            int ssthresh_var = 0;
            for(int n=0; n<wnd; n++){
                if(cwnd2 < ssthresh){
                    cwnd2 += 1;
                }else{
                    if (++ssthresh_var == wnd) {
                        ssthresh_var = 0;
                        cwnd2 += 1;
                    }
                }
            }
        }
    }
    

    TCP/IP 源码的一个疑问:

    TCP/IP 详解 1 这样描述: 设置拥塞窗口 cwnd 为1, 发送 1 个报文段, 收到一个 ACK 后, cwnd 加 1, 然后发送 2 个, 收到 2 个 ACK 后, cwnd 加 2, 然后发送 4 个...

    这确实是指数增长, 但是, 因为延时 ACK 的存在, 发送的2个报文段可能只收到 1 个 ACK, 所以 cwnd 只增加 1 而不是增加 2. 在延时 ACK 影响的范围内, 这是线性增长.

    希望熟悉 TCP/IP 的朋友帮忙解惑, 十分感谢!

    Posted by ideawu at 2008-01-10 09:03:34
  • 2008-01-04

    生产者消费者模型中生产者的速度快于消费者时所产生的问题及其解决方法讨论

    Views: 10686 | No Comments

    在生产者消费者模型中, 如果生产者生产产品(在下文中, 和读者写者模型中的信息是同一个概念)的速度 P 快于消费者消费产品的速度 C, 那么当时间趋于无限时, 待处理的产品的数量也将是无限多. 在带有缓冲区的生产者消费者模型中, 这意味着缓冲区将被挤爆, 导致系统崩溃.

    下面两个条件可能导致系统的崩溃:

    1. 缓冲区是有限的.
    2. 缓冲区中的产品是有时效的.

    这两个条件是任何系统固有的, 不能消除. 所以为了避免系统崩溃, 可有以下的处理方法:

    1. 在系统可接受的范围内丢弃多出的产品, 使生产者的速度宏观上和消费者的速度相等.
    2. 实现一种抑制生产者生产速度的机制, 使生产者的速度和消费者的速度相等.
    3. 增加消费者的数量, 使其为至少 P/C.

    本文讨论的是以第 3 种为主要解决方法, 前两种为辅助解决方法时的情况. 消费者数量是有限制的, 因为消费者本身仍然需要消费其它资源.

    消费者有两个队列: 等待队列(WaitQueue)和工作队列(WorkQueue). 等待队列中包含当前系统中正在等待产品的消费者. 工作队列中包含当前系统中正在处理产品的消费者.

    用信号量来实现的伪代码:

    // 运行中的 BeginWait 的实例上限.
    int MAX_WAIT = 1;
    Semaphore waitSemaphore = new Semaphore(MAX_WAIT, MAX_WAIT);
    // 运行中的 DoWork 的实例上限.
    int MAX_WORK = 3;
    Semaphore workSemaphore = new Semaphore(MAX_WORK, MAX_WORK);
    
    void BeginWait(callback){
        // 该方法将回调函数注册到某个系统中, 一旦等待的信号出现, 这个系统将调用回调函数.
        // 例如, 可以利用操作系统的信号机制.
        // 无论如何, 该方法的实现必须是几乎"立即"返回的.
    }
    
    // 本方法的实例上限 = min(MAX_WORK * 2 - 1, MAX_WORK + MAX_WAIT - 1)
    void EndWait() {
        waitSemaphore.Release();    
        DoWork();
        if (waitSemaphore.WaitOne(0, false)) {
            BeginWait(this.EndWait);
        }
    }
    
    void DoWork(){
        workSemaphore.WaitOne();
        if (workSemaphore.WaitOne(0, false)) {
            if (waitSemaphore.WaitOne(0, false)) {
                BeginWait(this.EndWait);
            }
            workSemaphore.Release();
        }
        // 进行操作, 花费一些时间...
        workSemaphore.Release();
    }
    

    因为注册一个回调函数的花费可以忽略不计, 所以建议 MAX_WAIT 的值不要大于 1. 由于算法本身的实现, MAX_WAIT 大于 MAX_WORK 时和等于时的效果是相同的.

    完整的可运行的 C# 源代码

    下面的代码需要在 C# 编译器 8, 和 .Net 框架 2 下编译和运行. 运行它, 你可以观察到生产者和消费者的实例数目.

    using System;
    using System.Threading;
    
    class Test
    {
        public static void Main(){
            Class1 c = new  Class1();
            c.Run();
        }
    }
    
    class Class1
    {
        private Random rand = new Random();
        private const int MAX_WAIT = 1;
        private Semaphore waitSemaphore = new Semaphore(MAX_WAIT, MAX_WAIT);
        private const int MAX_WORK = 6;
        private Semaphore workSemaphore = new Semaphore(MAX_WORK, MAX_WORK);
        // 将 Interlocked.Increment(ref num) 放到一段代码的开始处,
        // 同时将 Interlocked.Decrement(ref num); 放到这段代码的结束处,
        // 你将可以看到这段代码在操作系统中的运行实例数目.        
        private int num = 0;
    
        public void Run() {
            waitSemaphore.WaitOne();
            BeginWait(this.EndWait);
            Console.WriteLine("Started");
            while(true){
                Thread.Sleep(500);
                Console.WriteLine(
                    "num={0,2}, working={1,3}. waiting={2,3}. maxWorkerId={3,3}, maxWaitId={4,3}",
                    num, maxWorkerId - minWorkerId, maxWaitId - minWaitId, maxWorkerId, maxWaitId);
            }
        }
    
        private void EndWait(IAsyncResult ar) {
            Interlocked.Increment(ref num);
            waitSemaphore.Release();
            DoWork();
            if (waitSemaphore.WaitOne(0, false)) {
                BeginWait(this.EndWait);
            }
            Interlocked.Decrement(ref num);
        }
    
        // 工作中的消费者的标识.
        private int maxWorkerId = 0;
        private int minWorkerId = 0;
    
        private void DoWork() {
            Interlocked.Increment(ref maxWorkerId);        
            workSemaphore.WaitOne();
            // 应该实现一种计数锁. SemaphoreLock?
            if (workSemaphore.WaitOne(0, false)) {
                if(waitSemaphore.WaitOne(0, false)){
                    BeginWait(this.EndWait);
                }
                workSemaphore.Release();
            }
            Thread.Sleep(rand.Next(2000) + 2000);
            workSemaphore.Release();
            Interlocked.Increment(ref minWorkerId);
        }
    
        delegate void WaitDelegate();
    
        private void BeginWait(AsyncCallback callback) {
            WaitDelegate wait = this.Wait;
            // 使用异步委托, 所以 BeginWait 应该会立即返回.
            wait.BeginInvoke(callback, null);
        }
    
        // 等待中的消费者标识.
        private int maxWaitId = 0;
        private int minWaitId = 0;
    
        private void Wait() {
            Interlocked.Increment(ref maxWaitId);
            Thread.Sleep(rand.Next(2000) + 200);
            Interlocked.Increment(ref minWaitId);
        }
    
    }
    

    2008-01-08 更新

    Posted by ideawu at 2008-01-04 11:04:23
|<<<34567891011>>>| 9/11 Pages, 62 Results.