您的位置:首页 > 其它

自己实现一个Semaphore

2007-12-15 22:15 302 查看
其实这是我boss的想法,我一开始听他这么说也觉得比较差异,ms已经写好了何必再自己写一个.答案有两个:1MS写的东西未必就是最好的,如完成端口,heap等.2semaphore是多线程编程中的核心元素所以有必要提速.我们都知道在多线程中ms提供的多个现成阻塞核心对象中critical(monitor),mutex,semaphore,event(EventWaitHandle),event的代价是最小的,所以我们就选择用event来实现semaphore.

event是一个不完整的semaphore,它不能连续唤醒多个阻塞的线程.而为了能够自动的,连续的唤醒阻塞的线程,我想到了半自动步枪的发射原理:当枪膛中的一颗子弹发射后由于后座力会使枪机在自动的从弹夹中压入一个子弹进入枪膛.将这个想法运用到这里就是:当一个线程被唤醒后就相当于打出一颗子弹,它需要再激发一次event已达到自动压子弹的目的.

当然仅使用event是不够的,因为还要处理枪膛里是否有子弹和当前子弹数这两个临界条件.而为了提高性能我们不能使用普通的锁(critical或mutex)还有一个原因是我说过semaphore是多线程编程中最核心的元素,锁就应该用它来实现.因此在这里我们选择使用Interlocked(内锁,它的性能很高在纳秒级别.通过降低控制粒度可以很好的提高并发程序的效率)实际上在这个设计中event只是为了让线程阻塞用的,而这两个临界条件才是关键.

下面进入代码部分: 我们需要三个成员变量,是否可以开火(枪膛中是否有子弹),子弹数和押子弹

int _canFire; //这里不用bool是因为 Interlocked.Exchange方法不接受bool类型
long _bulletCount;
EventWaitHandle _clipBullet;

public Semaphore() : this(0) { }
public Semaphore(int size) //我认为Semaphore的最大可用数为无限
{
this._clipBullet = new EventWaitHandle(false, EventResetMode.AutoReset);
this.Post(size);
}

private void LoadBullet() //子弹上膛
{
if (Atomic.Exchange(ref this._canFire, 1) == 0) //如果子弹上膛了就不触发事件
this._clipBullet.Set();
}

public void Release()
{
Interlocked.Increase(ref this._bulletCount); //增加子弹数,对于多于1的使用add方法
this.LoadBullet();
}

public void Release(int count)
{
Interlocked.Add(ref this._bulletCount, (long)count);
if (count > 0)
this.LoadBullet();
}

public bool WaitOne()
{
//检查枪膛里是否有子弹,如果没有等待押子弹事件
for (; Interlocked.Exchange(ref this._canFire, 0) == 0; )
{
this._clipBullet.WaitOne();
}

if (Atomic.Decrement(ref this._bulletCount) > 0) //如果还有子弹则自动压入一颗
this.LoadBullet();
return true;
}

public bool Try_Wait() //尝试等待,相当于WaitOne(0) 这是一个很常用的功能所以要单独处理
{
if (Interlocked.CompareExchange(ref this._canFire, 0, 1) != 1) //这个函数就是大名鼎鼎的CAS
return false;
if (Interlocked.Decrement(ref this._bulletCount) > 0)
this.LoadBullet();

return true;
}

在c++环境下测试比windows原始semaphore快将近2被,在.net下没有明显效果.我觉得这和虚机的调度实现有关系.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐