文章导航PC6首页软件下载单机游戏安卓资源苹果资源

pc软件新闻网络操作系统办公工具编程服务器软件评测

安卓新闻资讯应用教程刷机教程安卓游戏攻略tv资讯深度阅读综合安卓评测

苹果ios资讯苹果手机越狱备份教程美化教程ios软件教程mac教程

单机游戏角色扮演即时战略动作射击棋牌游戏体育竞技模拟经营其它游戏游戏工具

网游cf活动dnf活动lol周免英雄lol礼包

手游最新动态手游评测手游活动新游预告手游问答

您的位置:首页资讯编程开发 → C#多线程编程实例实战

C#多线程编程实例实战

时间:2004/10/15 2:46:00来源:本站整理作者:蓝点我要评论(0)

单个写入程序/多个阅读程序在.Net类库中其实已经提供了实现,即System.Threading.ReaderWriterLock类。本文通过对常见的单个写入/多个阅读程序的分析来探索c#的多线程编程。

 

问题的提出

 

所谓单个写入程序/多个阅读程序的线程同步问题,是指任意数量的线程访问共享资源时,写入程序(线程)需要修改共享资源,而阅读程序(线程)需要读取数据。在这个同步问题中,很容易得到下面二个要求:

 

1) 当一个线程正在写入数据时,其他线程不能写,也不能读。

 

2) 当一个线程正在读入数据时,其他线程不能写,但能够读。

 

在数据库应用程序环境中经常遇到这样的问题。比如说,有n个最终用户,他们都要同时访问同一个数据库。其中有m个用户要将数据存入数据库,n-m个用户要读取数据库中的记录。

 

很显然,在这个环境中,我们不能让两个或两个以上的用户同时更新同一条记录,如果两个或两个以上的用户都试图同时修改同一记录,那么该记录中的信息就会被破坏。

 

我们也不让一个用户更新数据库记录的同时,让另一用户读取记录的内容。因为读取的记录很有可能同时包含了更新和没有更新的信息,也就是说这条记录是无效的记录。

 

实现分析

 

规定任一线程要对资源进行写或读操作前必须申请锁。根据操作的不同,分为阅读锁和写入锁,操作完成之后应释放相应的锁。将单个写入程序/多个阅读程序的要求改变一下,可以得到如下的形式:

 

一个线程申请阅读锁的成功条件是:当前没有活动的写入线程。

 

一个线程申请写入锁的成功条件是:当前没有任何活动(对锁而言)的线程。

 

因此,为了标志是否有活动的线程,以及是写入还是阅读线程,引入一个变量m_nActive,如果m_nActive > 0,则表示当前活动阅读线程的数目,如果m_nActive=0,则表示没有任何活动线程,m_nActive <0,表示当前有写入线程在活动,注意m_nActive<0,时只能取-1的值,因为只允许有一个写入线程活动。

 

为了判断当前活动线程拥有的锁的类型,我们采用了线程局部存储技术(请参阅其它参考书籍),将线程与特殊标志位关联起来。

 

申请阅读锁的函数原型为:public void AcquireReaderLock( int millisecondsTimeout ),其中的参数为线程等待调度的时间。函数定义如下:

 

public void AcquireReaderLock( int millisecondsTimeout )

 

{

 

// m_mutext很快可以得到,以便进入临界区

 

m_mutex.WaitOne( );

 

// 是否有写入线程存在

 

bool bExistingWriter = ( m_nActive < 0 );

 

if( bExistingWriter )

 

{ //等待阅读线程数目加1,当有锁释放时,根据此数目来调度线程

 

m_nWaitingReaders++;

 

}

 

else

 

{ //当前活动线程加1

 

m_nActive++;

 

}

 

m_mutex.ReleaseMutex();

 

//存储锁标志为Reader

 

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName);

 

object obj = Thread.GetData( slot );

 

LockFlags flag = LockFlags.None;

 

if( obj != null )

 

flag = (LockFlags)obj ;

 

if( flag == LockFlags.None )

 

{

 

Thread.SetData( slot, LockFlags.Reader );

 

}

 

else

 

{

 

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Reader ) );

 

}

 

 

if( bExistingWriter )

 

{ //等待指定的时间

 

this.m_aeReaders.WaitOne( millisecondsTimeout, true );

 

}

 

}

 

它首先进入临界区(用以在多线程环境下保证活动线程数目的操作的正确性)判断当前活动线程的数目,如果有写线程(m_nActive<0)存在,则等待指定的时间并且等待的阅读线程数目加1。如果当前活动线程是读线程(m_nActive>=0),则可以让读线程继续运行。



申请写入锁的函数原型为:public void AcquireWriterLock( int millisecondsTimeout ),其中的参数为等待调度的时间。函数定义如下:

 

public void AcquireWriterLock( int millisecondsTimeout )

 

{

 

// m_mutext很快可以得到,以便进入临界区

 

m_mutex.WaitOne( );

 

// 是否有活动线程存在

 

bool bNoActive = m_nActive == 0;

 

if( !bNoActive )

 

{

 

m_nWaitingWriters++;

 

}

 

else

 

{

 

m_nActive--;

 

}

 

m_mutex.ReleaseMutex();

 

//存储线程锁标志

 

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot( "myReaderWriterLockDataSlot" );

 

object obj = Thread.GetData( slot );

 

LockFlags flag = LockFlags.None;

 

if( obj != null )

 

flag = (LockFlags)Thread.GetData( slot );

 

if( flag == LockFlags.None )

 

{

 

Thread.SetData( slot, LockFlags.Writer );

 

}

 

else

 

{

 

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Writer ) );

 

}

 

//如果有活动线程,等待指定的时间

 

if( !bNoActive )

 

this.m_aeWriters.WaitOne( millisecondsTimeout, true );

 

}

 

它首先进入临界区判断当前活动线程的数目,如果当前有活动线程存在,不管是写线程还是读线程(m_nActive),线程将等待指定的时间并且等待的写入线程数目加1,否则线程拥有写的权限。

 

释放阅读锁的函数原型为:public void ReleaseReaderLock()。函数定义如下:

 

public void ReleaseReaderLock()

 

{

 

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName );

 

LockFlags flag = (LockFlags)Thread.GetData( slot );

 

if( flag == LockFlags.None )

 

{

return;

 

}

 

bool bReader = true;

 

switch( flag )

 

{

 

case LockFlags.None:

 

break;

 

case LockFlags.Writer:

 

bReader = false;

 

break;

 

}

 

if( !bReader )

 

return;

 

Thread.SetData( slot, LockFlags.None );

 

m_mutex.WaitOne();

 

AutoResetEvent autoresetevent = null;

 

this.m_nActive --;

 

if( this.m_nActive == 0 )

 

{

 

if( this.m_nWaitingReaders > 0 )

 

{

 

m_nActive ++ ;

 

m_nWaitingReaders --;

 

autoresetevent = this.m_aeReaders;

 

}

 

else if( this.m_nWaitingWriters > 0)

 

{

 

m_nWaitingWriters--;

 

m_nActive --;

 

autoresetevent = this.m_aeWriters ;

 

}

 

}

 

m_mutex.ReleaseMutex();

 

if( autoresetevent != null )

 

autoresetevent.Set();

 

}

 

 

释放阅读锁时,首先判断当前线程是否拥有阅读锁(通过线程局部存储的标志),然后判断是否有等待的阅读线程,如果有,先将当前活动线程加1,等待阅读线程数目减1,然后置事件为有信号。如果没有等待的阅读线程,判断是否有等待的写入线程,如果有则活动线程数目减1,等待的写入线程数目减1。释放写入锁与释放阅读锁的过程基本一致,可以参看源代码。

 

注意在程序中,释放锁时,只会唤醒一个阅读程序,这是因为使用AutoResetEvent的原历,读者可自行将其改成ManualResetEvent,同时唤醒多个阅读程序,此时应令m_nActive等于整个等待的阅读线程数目。



申请写入锁的函数原型为:public void AcquireWriterLock( int millisecondsTimeout ),其中的参数为等待调度的时间。函数定义如下:

 

public void AcquireWriterLock( int millisecondsTimeout )

 

{

 

// m_mutext很快可以得到,以便进入临界区

 

m_mutex.WaitOne( );

 

// 是否有活动线程存在

 

bool bNoActive = m_nActive == 0;

 

if( !bNoActive )

 

{

 

m_nWaitingWriters++;

 

}

 

else

 

{

 

m_nActive--;

 

}

 

m_mutex.ReleaseMutex();

 

//存储线程锁标志

 

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot( "myReaderWriterLockDataSlot" );

 

object obj = Thread.GetData( slot );

 

LockFlags flag = LockFlags.None;

 

if( obj != null )

 

flag = (LockFlags)Thread.GetData( slot );

 

if( flag == LockFlags.None )

 

{

 

Thread.SetData( slot, LockFlags.Writer );

 

}

 

else

 

{

 

Thread.SetData( slot, (LockFlags)((int)flag | (int)LockFlags.Writer ) );

 

}

 

//如果有活动线程,等待指定的时间

 

if( !bNoActive )

 

this.m_aeWriters.WaitOne( millisecondsTimeout, true );

 

}

 

它首先进入临界区判断当前活动线程的数目,如果当前有活动线程存在,不管是写线程还是读线程(m_nActive),线程将等待指定的时间并且等待的写入线程数目加1,否则线程拥有写的权限。

 

释放阅读锁的函数原型为:public void ReleaseReaderLock()。函数定义如下:

 

public void ReleaseReaderLock()

 

{

 

System.LocalDataStoreSlot slot = Thread.GetNamedDataSlot(m_strThreadSlotName );

 

LockFlags flag = (LockFlags)Thread.GetData( slot );

 

if( flag == LockFlags.None )

 

{

return;

 

}

 

bool bReader = true;

 

switch( flag )

 

{

 

case LockFlags.None:

 

break;

 

case LockFlags.Writer:

 

bReader = false;

 

break;

 

}

 

if( !bReader )

 

return;

 

Thread.SetData( slot, LockFlags.None );

 

m_mutex.WaitOne();

 

AutoResetEvent autoresetevent = null;

 

this.m_nActive --;

 

if( this.m_nActive == 0 )

 

{

 

if( this.m_nWaitingReaders > 0 )

 

{

 

m_nActive ++ ;

 

m_nWaitingReaders --;

 

autoresetevent = this.m_aeReaders;

 

}

 

else if( this.m_nWaitingWriters > 0)

 

{

 

m_nWaitingWriters--;

 

m_nActive --;

 

autoresetevent = this.m_aeWriters ;

 

}

 

}

 

m_mutex.ReleaseMutex();

 

if( autoresetevent != null )

 

autoresetevent.Set();

 

}

 

 

释放阅读锁时,首先判断当前线程是否拥有阅读锁(通过线程局部存储的标志),然后判断是否有等待的阅读线程,如果有,先将当前活动线程加1,等待阅读线程数目减1,然后置事件为有信号。如果没有等待的阅读线程,判断是否有等待的写入线程,如果有则活动线程数目减1,等待的写入线程数目减1。释放写入锁与释放阅读锁的过程基本一致,可以参看源代码。

 

注意在程序中,释放锁时,只会唤醒一个阅读程序,这是因为使用AutoResetEvent的原历,读者可自行将其改成ManualResetEvent,同时唤醒多个阅读程序,此时应令m_nActive等于整个等待的阅读线程数目。

相关阅读 Windows错误代码大全 Windows错误代码查询激活windows有什么用Mac QQ和Windows QQ聊天记录怎么合并 Mac QQ和Windows QQ聊天记录Windows 10自动更新怎么关闭 如何关闭Windows 10自动更新windows 10 rs4快速预览版17017下载错误问题Win10秋季创意者更新16291更新了什么 win10 16291更新内容windows10秋季创意者更新时间 windows10秋季创意者更新内容kb3150513补丁更新了什么 Windows 10补丁kb3150513是什么

文章评论
发表评论

热门文章 没有查询到任何记录。

最新文章 编程语言排行榜2020年android studio怎么删 eclipse怎么设置编码格式 eclipse设置编码格andriod studio如何使用真机测试 andriod sandroid studio怎么生成apk android studioandroid studio如何导入jar包 android stud

人气排行 安卓模拟器BlueStacks安装使用教程编程语言排行榜2020年9月 TIOBE编程语言排行eclipse字体大小怎么设置 eclipse字体大小plsql developer怎么连接数据库 plsql deveTomcat9.0安装教程 Tomcat9.0环境变量配置方plsql developer怎么使用 plsql developerVisual Studio 2015环境搭建教程Eclipse优化设置教程 Eclipse优化设置技巧