.Net组件程序设计之线程、并发管理(二)

2.同步线程

  • 手动同步

    • 监视器

    • 互斥

    • 可等待事件

同步线程

所有的.NET组件都支持在多线程的环境中运行,可以被多个线程并发访问,如果没有线程同步,这样的后果是当多个线程同时访问 对象状态时,对象的状态可能被破坏,造成不一致性。.NET提供了两种方法来避免这样的问题,使得我们设计的组件更加健壮。 第一种是自动同步,让你使用一个属性来修饰组件,这样就可以把组件交给.NET了,同步的事情也就交给了.NET。 第二种是手动同步,这是让你使用.NET提供的同步对象来实现线程同步,也不是太复杂,本篇将会对手动同步来稍作讲解。

2.1 手动同步

.NET手动同步提供了一套丰富的同步锁,上一节说到同步域,同步域事实上是一个巨大的宏锁,而手动同步则提供了 对被锁对象的细粒度控制,可以控制访问对象、单一成员甚至是单行的代码。这样的好处就是有可能的提高系统的性能和吞吐量。

2.1.1 监视器

监视器是一种只能和引用类型一块工作的锁。

2.1.1-1

     public class ManualSynchronization
{
public void DoSomeThing()
{
for (int i = ; i < ; i++)
{
Console.WriteLine(i.ToString());
}
}
}
 ManualSynchronization monitorcase = new ManualSynchronization();

 Monitor.Enter(monitorcase);
try
{
monitorcase.DoSomeThing();
}
finally
{
Monitor.Exit(monitorcase);
}

任何线程的任何对象都可以调用Enter()方法来锁定对象,如果Monitor正在被一个线程使用,而这个时候又有一个线程来请求对象Enter(),这样就会使第二个线程阻塞,直到第一个线程调用Exit(),如果这时有多个线程请求对象Enter(),它们就会被放置在一个叫做锁队列的队列里,并依照队列的顺序获得服务顺序。

你还可以使用Monitor类为静态类方法或静态属性提供安全线程访问,方法是让Monitor锁定该类型,而不是一个实例:

2.1.1-2

     public class ManualSynchronization
{
public static void SDoSomeThing()
{
for (int i = ; i < ; i++)
{
Console.WriteLine(i.ToString());
}
}
}
            Monitor.Enter(typeof(ManualSynchronization));
try
{
ManualSynchronization.SDoSomeThing();
}
finally
{
Monitor.Exit(typeof(ManualSynchronization));
}

在C#中为了简化这样的写法,提供了lock语句,使编译器在try/finally语句中自动产生对Enter()和Exit()的调用。

比如你写下这样的代码等同于2.1.1-1的示例代码:

2.1.1-3

 ManualSynchronization monitorcase = new ManualSynchronization();
lock(monitorcase)
{
monitorcase.DoSomeThing();
}

像上面的代码这样写看似没什么问题了,因为这个lock所定对象实例或者是对象类型,是根据客户端开发者的判断而定的,这样的锁定方式与客户端耦合度大,看下以下代码:

2.1.1-4

     public class ManualSynchronization
{
public void DoSomeThing()
{
lock (this)
{
for (int i = ; i < ; i++)
{
Console.WriteLine(i.ToString());
}
}
}
 ManualSynchronization monitorcase = new ManualSynchronization();
monitorcase.DoSomeThing();

这样感觉是不是舒服不少,这就是方法同步了,.NET内部也对它提供了支持,定义在System.Runtime.CompilerServices命名空间里的MethodImpl方法属性接受一个MethodImplOptions类型的枚举。其中一个枚举值是MethodImplOptions.Synchronized。当运行这个枚举值的时候,编辑器就指示.NET运行时在方法入口锁定对象,语义和2.1.1-4的代码断相同:

2.1.1-5

     public class ManualSynchronization
{
[MethodImpl( MethodImplOptions.Synchronized)]
public void DoSomeThingSynchroniezd()
{
Console.WriteLine("studycase");
}

2.1.2 互斥

这一个小节要讲到的是Mutex类,它是从WaitHandle派生的类,它保证了各个线程在某个资源或代码块上相互排斥。

2.1.2-1

     public class MutexDom:IDisposable
{
public MutexDom(){}
private int _Num = ;
public int Num
{
get
{
return _Num;
}
set
{
_Num = value;
}
}
public void Dom()
{
for (int i = ; i < ; i++)
{
Num = Num + i;
Console.WriteLine(Thread.CurrentThread.Name + "_" + Num.ToString() +"_"+Thread.CurrentThread.ManagedThreadId.ToString());
} }
public void Dispose()
{ } public static void Test()
{
MutexDom mutexDom=new MutexDom();
ThreadStart threadStart=new ThreadStart(mutexDom.Dom);
Thread thread1 = new Thread(threadStart);
thread1.Name = "Thread_One";
Thread thread2 = new Thread(threadStart);
thread2.Name = "Thread_Two";
thread1.Start();
thread2.Start();
}
}

MutexDom.Test();启动测试,我所希望的效果是Dom()方法是有序的执行的,而我用了一个int类型的Nun属性来作为计数器,那我们就一起来看一下结果吧(可能每次运行结果不一样)

我所期望的在线程Thread_One中执行0递增至99的值时4950,而在结果中已经超出了这个范围,这说明了什么?说明了两个线程在交替的对Num进行操作。修改一下代码,再来看一下:

2.1.2-2

     public class MutexDom:IDisposable
{
private Mutex _Mutex;
public MutexDom()
{
_Mutex = new Mutex();
}
private int _Num = ;
public int Num
{
get
{
return _Num;
}
set
{
_Num = value;
}
}
public void Dom()
{
_Mutex.WaitOne();//如果当前资源被占用 则等待占用它的线程发送消息
try
{
for (int i = ; i < ; i++)
{
Num = Num + i;
Console.WriteLine(Thread.CurrentThread.Name + "_" + Num.ToString() +"_"+Thread.CurrentThread.ManagedThreadId.ToString());
}
}
finally
{
_Mutex.ReleaseMutex();
} }
public void Dispose()
{
_Mutex.Close();
} public static void Test()
{
MutexDom mutexDom=new MutexDom();
ThreadStart threadStart=new ThreadStart(mutexDom.Dom);
Thread thread1 = new Thread(threadStart);
thread1.Name = "Thread_One";
Thread thread2 = new Thread(threadStart);
thread2.Name = "Thread_Two"; thread1.Start();
thread2.Start(); }
}

从结果中得出,是线程Thread_Two先执行的,这个没关系,只要看它的结果值就行了,这就说明了,在线程"Thread_Two"执行对Dom()方法操作的时候"Thread_One"是肯定已经启动了的,而且是在等待"Thread_Two"的释放消息,这样就保持了对象状态的一致性,这个时候"Thread_One"是在一个等待队列中的。如果这个时候"Thread_One"调用ReleaseMutex()方法,是会报错的,因为ReleaseMutex()方法是只能当前所占有的线程来进行释放,互斥就这样完成了。

2.1.3 可等待事件

EventWaitHandle类派生于WaitHandle,被用于跨线程通知事件。 它有两种状态:信号已发状态、信号未发状态。 Set()方法和 Reset()方法分别把句柄状态设置为信号已发或信号未发。 它有两种使用方式,一种是手动重置,还有一种是自动重置。是通过给构造函数提供一个EventResetMode类型的枚举值,

     public enum EventResetMode
{
AutoReset,
ManualReset
}

.NET提供了EventWaitHandle的两个强类型子类,定义如下:

     public class ManualResetEvent:EventWaitHandle
{
public ManualResetEvent(bool initialState):base(initialState,EventResetMode.ManualReset)
{}
}
public sealed class AutoResetEvent : EventWaitHandle
{
public AutoResetEvent(bool initialState):base(initialState,EventResetMode.AutoReset)
{}
}

先来看一下手动重置:

2.1.3-1

     public class EventDom:IDisposable
{
ManualResetEvent _WaitHandle;
public EventDom()
{
_WaitHandle = new ManualResetEvent(true); Thread thread = new Thread(DoWork);
thread.Start();
}
private void DoWork()
{
int num = ;
while (true)
{
_WaitHandle.WaitOne();
num++;
Console.WriteLine("EventDom_" + num.ToString());
}
}
public void StartThread()
{
_WaitHandle.Set();
Console.WriteLine("EventDom->StartThread");
}
public void StopThread()
{
_WaitHandle.Reset();
Console.WriteLine("EventDom->StopThread");
}
public void Dispose()
{
_WaitHandle.Close();
} public static void Test()
{
EventDom eventDom = new EventDom();
eventDom.StopThread();
} }

调用EventDom.Test();进行测试,结果如下图:

在构造函数中我就已经把手动重置事件声明为了 信号已发状态,所以在运行的时候,while在每次循环的时候等待接收到的信号一直都是已发送状态,所以是一直在输出,直到调用了StopThread()方法中的Reset()方法,把状态设置为未发送状态,才使执行暂停。

再来看一下自动重置,修改一下上段的代码,

     public class EventDom : IDisposable
{
AutoResetEvent _WaitHandle;
public EventDom()
{
_WaitHandle = new AutoResetEvent(true); Thread thread = new Thread(DoWork);
thread.Start();
}
private void DoWork()
{
int num = ;
while (true)
{
_WaitHandle.WaitOne();
num++;
Console.WriteLine("EventDom_" + num.ToString());
}
}
public void StartThread()
{
_WaitHandle.Set();
Console.WriteLine("EventDom->StartThread");
}
public void StopThread()
{
_WaitHandle.Reset();
Console.WriteLine("EventDom->StopThread");
}
public void Dispose()
{
_WaitHandle.Close();
} public static void Test()
{
EventDom eventDom = new EventDom();
eventDom.StartThread();
}
}

首先把手动重置类型换成了自动重置类型,然后再测试代码中把设置状态为未发送的方法,改成了设置状态为已发送的方法。

这个结果是正确,因为自动重置类型就是事件状态被设置为信号已发,它就会保持这个状态,直到某个线程从等待调用中释放出来,然后在这个时候,它的状态会发生改变,自动的反转到未发送状态。

还有一些扩展的知识点就不在这一一阐述了,希望本篇能对大家有所帮助。END

作者:金源

出处:http://www.cnblogs.com/jin-yuan/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面

.NET组件程序设计之线程、并发管理(二)的更多相关文章

  1. .Net组件程序设计之线程、并发管理(一)

    .Net组件程序设计之线程.并发管理(一) 1.线程 线程 线程的创建 线程的阻塞 线程挂起 线程睡眠 加入线程 线程中止 现在几乎所有的应用程序都是多线程的,给用户看来就是一个应用程序界面(应用程序 ...

  2. .Net组件程序设计之远程调用(二)

    .Net组件程序设计之远程调用(二) 激活模式 引用封送对象激活类型两种, 一种是客户端激活类型,一种是服务器端激活. 客户端激活对象 客户端激活方式:当客户端创建一个远程对象时,客户端得到的是一个新 ...

  3. 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

    前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...

  4. 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁

    上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和基本的方法,显示了怎样 ...

  5. 谈论高并发(二十二)解决java.util.concurrent各种组件(四) 深入了解AQS(二)

    上一页介绍AQS其基本设计思路以及两个内部类Node和ConditionObject实现 聊聊高并发(二十一)解析java.util.concurrent各个组件(三) 深入理解AQS(一) 这篇说一 ...

  6. .Net组件程序设计之异步调用

    .Net组件程序设计之异步调用 说到异步调用,在脑海中首先想到就是BeginInvoke(),在一些常用对象中我们也会常常见到Invoke()和BeginInvoke(), 要想让自己的组件可以被客户 ...

  7. .Net组件程序设计之上下文

    .Net组件程序设计之上下文 在后续篇幅的远程调用的文章里有说到应用程序域,那是大粒度的控制程序集的逻辑存在,那么想对对象的控制又由谁来做主呢?没错了,就是上下文.CLR把应用程序域更细化了,在应用程 ...

  8. (转载)JAVA线程池管理

    平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...

  9. Linux线程学习(二)

    线程基础 进程 系统中程序执行和资源分配的基本单位 每个进程有自己的数据段.代码段和堆栈段 在进行切换时需要有比较复杂的上下文切换   线程 减少处理机的空转时间,支持多处理器以及减少上下文切换开销, ...

随机推荐

  1. FZU 2137 奇异字符串 后缀树组+RMQ

    题目连接:http://acm.fzu.edu.cn/problem.php?pid=2137 题解: 枚举x位置,向左右延伸计算答案 如何计算答案:对字符串建立SA,那么对于想双延伸的长度L,假如有 ...

  2. SQL函数说明大全

    一旦成功地从表中检索出数据,就需要进一步操纵这些数据,以获得有用或有意义的结果.这些要求包括:执行计算与数学运算.转换数据.解析数值.组合值和聚合一个范围内的值等. 下表给出了T-SQL函数的类别和描 ...

  3. TOMCAT-报错The BASEDIR environment variable is not defined correctly

    <span style="font-size:18px;">The BASEDIR environment variable is not defined correc ...

  4. C#开发中常用方法3------Cookie的存取

    ---------------------------------------------------------------------------------------------------- ...

  5. CVE-2015-7645 analyze and exploit

    Hack team之后adobe和google合作对flash进行了大改,一度提高了flash的利用门槛,CVE-2015-7645作为第一个突破这些限制的漏洞利用方式,可以作为vetect利用方式之 ...

  6. 《DSP using MATLAB》示例Example5.23

    代码: conv_time = zeros(1,150); fft_time = zeros(1, 150); % % Nmax = 2048; for L = 1:150 tc = 0; tf = ...

  7. 关于3DSMAX中opensubdiv细分功能的笔记

    说到建模和细分,估计用过3dsmax的同学就会心有余悸,每次添加"涡轮平滑"或者"网格平滑"之前,都会下意识的进行保存,没有为啥,就是因为太容易使软件挂掉了. ...

  8. [转]iOS Safari 中click点击事件失效的解决办法

    iOS Safari 中click点击事件失效的解决办法 问题起因: 在微信公众号开发(微站)过程中用jquery的live方法绑定的click事件点击无效(不能执行) 问题描述 当使用委托给一个元素 ...

  9. Github fork同步

    做到以下几个步骤: 添加远程上游工程repository. git remote add upstream git@github.com:*.git git remote -v 将上游工程代码合并到本 ...

  10. BZOJ2498 : Xavier is Learning to Count

    考虑容斥,通过$Bell(p)$的时间枚举所有等价情况. 对于一种情况,强制了一个等价类里面的数都要相同,其它的可以相同也可以不同. 这方案数显然可以通过多项式乘法求得,乘上容斥系数$(-1)^{p- ...