最近跳槽刚到新公司,初期也没啥事,修改了几个小功能以后就是熟悉代码。实话说干巴看代码真没什么意思,一个字的文档都没有。看到一个基础类时发现有多线程的应用,反正左右无事就把多线程的东西收集一下。以免自己以后忘了,下面的东西大部分都是从别人那直接粘过来的,我会注明出处。
首先是AutoResetEvent的使用,这个是我看msdn后自己写的例子,呵呵
msdn地址:http://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent%28VS.80%29.aspx
AutoResetEvent主要是为了线程之间等待用的,就是执行时有个先后顺序;还有个ManualResetEvent也是同样的作用,不过AutoResetEvent调用Set()方法后,会自动又将信号置为不发送状态ManualResetEvent不会,这个我是从《AutoResetEvent 与 ManualResetEvent区别》这里看来的。
不多说废话,贴我自己写的小例子
AutoResetEvent使用
class Program
{
static void Main(string[] args)
{
AutoResetEvent mainEvent = new AutoResetEvent(false);
Thread t = new Thread(ThreadPoolTest.WorkItemMethodOne);
t.Start();
ThreadPool.QueueUserWorkItem(new
WaitCallback(ThreadPoolTest.WorkItemMethodTwo), mainEvent);
// Since ThreadPool threads are background threads,
// wait for the work item to signal before ending Main.
mainEvent.WaitOne();
Console.WriteLine("\nThe end of the main function");
Console.ReadLine();
}
}
class ThreadPoolTest
{
private static String status;
private static ManualResetEvent manualEvent = new ManualResetEvent(false);
public static void WorkItemMethodOne()
{
Console.WriteLine("Starting WorkItem One.");
Console.WriteLine("Working......need 10 seconds.");
status = "WorkItem One is Completed!";
Thread.Sleep(10 * 1000);
Console.WriteLine("\nEnding WorkItem One.");
// Signal Main that the work item is finished.
manualEvent.Set();
}
public static void WorkItemMethodTwo(Object mainEvent)
{
Console.WriteLine("\nStarting WorkItem Two.");
Console.WriteLine("do some preparatory work!");
Console.WriteLine("waite for WorkItem One.");
//waite WorkItem One Completed
manualEvent.WaitOne();
Console.WriteLine("\nResult of the workWorkItem One:" + status);
Console.WriteLine("Ending WorkItem Two.");
// Signal Main that the work item is finished.
((AutoResetEvent)mainEvent).Set();
}
}
代码不少,大家见谅啊!水平有限,写短了表达不出来意思。
下面是一些从网上收集的知识。怕以后找不到所以贴到自己博客里
原文链接《c#之多线程基础知识》
【特此声明以下内容从《c#之多线程基础知识》转载而来】
1、什么是进程?
当一个程序开始运行时,它就是一个进程,进程所指包括运行中的程序和程序所使用到的内存和系统资源。
2、什么是线程?
一个进程由多个线程所组成的,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
3、什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
我们以前学习过的统筹方法,实际上就是一种多线程的应用。一件事,我们可以分成几个几部分来做,可以并行来完成,节省时间。当然这个和计算机实现的多线程是有许多不同的。浏览器就是一个多线程程序,可以边下载,边听歌,边浏览网页。
多线程可以提高CPU的利用率,但是使用不当也会带来问题,主要是内存开销和管理复杂。另外需要特别指出,在单核CPU的情况下,同一时间只能执行一个线程,是伪多线程,而在多核CPU上,多线程才可以真正的在多个processor同时执行。
1、当前线程
Thread.CurrentThread
2、线程控制
* Start():启动线程
* Sleep(int):静态方法,暂停当前线程指定的毫秒数
* Abort():通常使用该方法来终止一个线程,不可恢复的,再次执行Start会报错
* Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复
* Resume():恢复被Suspend()方法挂起的线程的执行
* Join():使主线程等待,直到当前线程结束
3、ThreadState属性
Thread.ThreadState这个属性代表了线程运行时状态:
* Aborted:线程已停止
* AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止
* Background:线程在后台执行,与属性Thread.IsBackground有关
* Running:线程正在正常运行
* Stopped:线程已经被停止
* StopRequested:线程正在被要求停止
* Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)
* SuspendRequested:线程正在要求被挂起,但是未来得及响应
* Unstarted:未调用Thread.Start()开始线程的运行
* WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态
4、优先级
可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。
//设定优先级为最低
myThread.Priority=ThreadPriority.Lowest;
5、多线程的例子
线程入口通过ThreadStart代理来提供的,可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所指向的函数。
查看源代码
打印?
class Program
{
static void Main(string[] args)
{
TestClass test = new TestClass();
Thread th1 = new Thread(new ThreadStart(test.ThreadMethod));
th1.Start();
//主线程暂停4ms,CPU执行线程th1
Thread.Sleep(4);
th1.Abort();
th1.Join();
Console.ReadLine();
}
}
public class TestClass
{
public void ThreadMethod()
{
while (true)
{
Console.WriteLine("线程1正在运行");
}
}
}
6、线程死锁
为了防止资源被同时使用,c#中使用lock和Monitor来锁定资源,处理完毕后再释放资源。其实在C#编译器编译lock语句时,lock编译成了调用Monitor类,所以完全可以使用Monitor替代lock。使用这种方法很可能就会导致死锁的情况。
两个线程抢占两个资源,线程1抢到了资源A,还需要资源B;线程2抢到了资源B,还需要资源A;结果就死锁了。
解决死锁问题,可以使用Monitor.TryEntry,设置超时时间
查看源代码
打印?
if(Monitor.TryEntry(lockObj, 1000)) {
try{
}
finally{
Monitor.Exit(lockObj);
}
}
else {
// 超时后的处理代码
}
还有一篇要转
原文链接《c# 线程同步: 详解lock,monitor,同步事件和等待句柄以及mutex》
【特此声明一下内容转载自《c# 线程同步: 详解lock,monitor,同步事件和等待句柄以及mutex》】
对于引用类型和非线程安全的资源的同步处理,有四种相关处理:lock关键字,监视器(Monitor), 同步事件和等待句柄, mutex类。
Lock关键字
本人愚钝,在以前编程中遇到lock的问题总是使用lock(this)一锁了之,出问题后翻看MSDN突然发现下面几行字:通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、 lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:如果实例可以被公共访问,将出现 lock (this) 问题。如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。来看看lock(this)的问题:如果有一个类 Class1,该类有一个方法用lock(this)来实现互斥:
public void Method2()
{
lock (this)
{
System.Windows.Forms.MessageBox.Show("Method2 End");
}
}
如果在同一个Class1的实例中,该Method2能够互斥的执行。但是如果是2个Class1的实例分别来执行Method2,是没有互斥效果的。因为这里的lock,只是对当前的实例对象进行了加锁。
Lock(typeof(MyType)) 锁定住的对象范围更为广泛,由于一个类的所有实例都只有一个类型对象(该对象是typeof的返回结果),锁定它,就锁定了该对象的所有实例,微软现在建议(原文请参考:http://www.microsoft.com/china/MSDN/library /enterprisedevelopment/softwaredev/SDaskgui06032003.mspx?mfr=true)不要使用 lock(typeof(MyType)),因为锁定类型对象是个很缓慢的过程,并且类中的其他线程、甚至在同一个应用程序域中运行的其他程序都可以访问该类型对象,因此,它们就有可能代替您锁定类型对象,完全阻止您的执行,从而导致你自己的代码的挂起。
锁住一个字符串更为神奇,只要字符串内容相同,就能引起程序挂起。原因是在.NET中,字符串会被暂时存放,如果两个变量的字符串内容相同的话,.NET会把暂存的字符串对象分配给该变量。所以如果有两个地方都在使用lock(“my lock”)的话,它们实际锁住的是同一个对象。到此,微软给出了个lock的建议用法:锁定一个私有的static 成员变量。
.NET在一些集合类中(比如ArrayList,HashTable,Queue,Stack)已经提供了一个供lock使用的对象SyncRoot,用Reflector工具查看了SyncRoot属性的代码,在Array中,该属性只有一句话:return this,这样和lock array的当前实例是一样的。ArrayList中的SyncRoot有所不同
get
{
if (this._syncRoot == null)
{
Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
}
return this._syncRoot;
其中Interlocked类是专门为多个线程共享的变量提供原子操作(如果你想锁定的对象是基本数据类型,那么请使用这个类),CompareExchange方法将当前syncRoot和null做比较,如果相等,就替换成new object(),这样做是为了保证多个线程在使用syncRoot时是线程安全的。集合类中还有一个方法是和同步相关的:Synchronized,该方法返回一个对应的集合类的 wrapper类,该类是线程安全的,因为他的大部分方法都用lock来进行了同步处理,比如Add方法:
public override void Add(object key, object value)
{
lock (this._table.SyncRoot)
{
this._table.Add(key, value);
}
}
这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合:
Queue myCollection = new Queue();
lock(myCollection.SyncRoot) {
foreach (Object item in myCollection) {
// Insert your code here.
}
}
Monitor类
该类功效和lock类似:
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
DoSomething();
}
finally
{
System.Threading.Monitor.Exit(obj);
}
lock 关键字比Monitor简洁,其实lock就是对Monitor的Enter和Exit的一个封装。另外Monitor还有几个常用的方法:TryEnter能够有效的决绝长期死等的问题,如果在一个并发经常发生,而且持续时间长的环境中使用TryEnter,可以有效防止死锁或者长时间的等待。比如我们可以设置一个等待时间bool gotLock = Monitor.TryEnter(myobject,1000),让当前线程在等待1000秒后根据返回的bool值来决定是否继续下面的操作。Pulse以及PulseAll还有Wait方法是成对使用的,它们能让你更精确的控制线程之间的并发,MSDN关于这3个方法的解释很含糊,有必要用一个具体的例子来说明一下:
using System.Threading;
public class Program {
static object ball = new object();
public static void Main() {
Thread threadPing = new Thread( ThreadPingProc );
Thread threadPong = new Thread( ThreadPongProc );
threadPing.Start(); threadPong.Start();
}
static void ThreadPongProc() {
System.Console.WriteLine("ThreadPong: Hello!");
lock ( ball )
for (int i = 0; i < 5; i++){
System.Console.WriteLine("ThreadPong: Pong ");
Monitor.Pulse( ball );
Monitor.Wait( ball );
}
System.Console.WriteLine("ThreadPong: Bye!");
}
static void ThreadPingProc() {
System.Console.WriteLine("ThreadPing: Hello!");
lock ( ball )
for(int i=0; i< 5; i++){
System.Console.WriteLine("ThreadPing: Ping ");
Monitor.Pulse( ball );
Monitor.Wait( ball );
}
System.Console.WriteLine("ThreadPing: Bye!");
}
}
执行结果如下(有可能是ThreadPong先执行):
ThreadPing: Hello!
ThreadPing: Ping
ThreadPong: Hello!
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Ping
ThreadPong: Pong
ThreadPing: Bye!
当 threadPing进程进入ThreadPingProc锁定ball并调用Monitor.Pulse( ball );后,它通知 threadPong从阻塞队列进入准备队列,当threadPing调用Monitor.Wait( ball )阻塞自己后,它放弃了了对ball的锁定,所以threadPong得以执行。PulseAll与Pulse方法类似,不过它是向所有在阻塞队列中的进程发送通知信号,如果只有一个线程被阻塞,那么请使用Pulse方法。
同步事件和等待句柄
同步事件和等待句柄用于解决更复杂的同步情况,比如一个一个大的计算步骤包含3个步骤result = first term + second term + third term,如果现在想写个多线程程序,同时计算 first term,second term 和third term,等所有3个步骤计算好后再把它们汇总起来,我们就需要使用到同步事件和等待句柄,同步事件分有两个,分别为AutoResetEvent和ManualResetEvent,这两个类可以用来代表某个线程的运行状态:终止和非终止,等待句柄用来判断ResetEvent的状态,如果是非终止状态就一直等待,否则放行,让等待句柄下面的代码继续运行。下面的代码示例阐释了如何使用等待句柄来发送复杂数字计算的不同阶段的完成信号。此计算的格式为:result = first term + second term + third term
using System;
using System.Threading;
class CalculateTest
{
static void Main()
{
Calculate calc = new Calculate();
Console.WriteLine("Result = {0}.",
calc.Result(234).ToString());
Console.WriteLine("Result = {0}.",
calc.Result(55).ToString());
}
}
class Calculate
{
double baseNumber, firstTerm, secondTerm, thirdTerm;
AutoResetEvent[] autoEvents;
ManualResetEvent manualEvent;
// Generate random numbers to simulate the actual calculations.
Random randomGenerator;
public Calculate()
{
autoEvents = new AutoResetEvent[]
{
new AutoResetEvent(false),
new AutoResetEvent(false),
new AutoResetEvent(false)
};
manualEvent = new ManualResetEvent(false);
}
void CalculateBase(object stateInfo)
{
baseNumber = randomGenerator.NextDouble();
// Signal that baseNumber is ready.
manualEvent.Set();
}
// The following CalculateX methods all perform the same
// series of steps as commented in CalculateFirstTerm.
void CalculateFirstTerm(object stateInfo)
{
// Perform a precalculation.
double preCalc = randomGenerator.NextDouble();
// Wait for baseNumber to be calculated.
manualEvent.WaitOne();
// Calculate the first term from preCalc and baseNumber.
firstTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
// Signal that the calculation is finished.
autoEvents[0].Set();
}
void CalculateSecondTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
secondTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[1].Set();
}
void CalculateThirdTerm(object stateInfo)
{
double preCalc = randomGenerator.NextDouble();
manualEvent.WaitOne();
thirdTerm = preCalc * baseNumber *
randomGenerator.NextDouble();
autoEvents[2].Set();
}
public double Result(int seed)
{
randomGenerator = new Random(seed);
// Simultaneously calculate the terms.
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateBase));
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateFirstTerm));
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateSecondTerm));
ThreadPool.QueueUserWorkItem(
new WaitCallback(CalculateThirdTerm));
// Wait for all of the terms to be calculated.
WaitHandle.WaitAll(autoEvents);
// Reset the wait handle for the next calculation.
manualEvent.Reset();
return firstTerm + secondTerm + thirdTerm;
}
}
该示例一共有4个ResetEvent,一个ManualEvent,三个AutoResetEvent,分别反映4个线程的运行状态。 ManualEvent和AutoResetEvent有一点不同:AutoResetEvent是在当前线程调用set方法激活某线程之后,AutoResetEvent状态自动重置,而ManualEvent则需要手动调用Reset方法来重置状态。接着来看看上面那段代码的执行顺序,Main方法首先调用的是Result 方法,Result方法开启4个线程分别去执行,主线程阻塞在 WaitHandle.WaitAll(autoEvents)处,等待3个计算步骤的完成。4个ResetEvent初始化状态都是非终止(构造实例时传入了false),CalculateBase首先执行完毕,其他3个线程阻塞在manualEvent.WaitOne()处,等待 CalculateBase执行完成。CalculateBase生成baseNumber后,把代表自己的ManualEvent状态设置为终止状态。其他几个线程从manualEvent.WaitOne()处恢复执行,在执行完自己的代码后把自己对应的AutoResetEvent状态置为终止。当 3个计算步骤执行完后,主线程从阻塞中恢复,把三个计算结果累加后返回。还要多补充一点的是WaitHandle的 WaitOne,WaitAll,WaitAny方法,如果等待多个进程就用WaitAll,如本例中的:WaitHandle.WaitAll(autoEvents),WaitAny是等待的线程中有一个结束则停止等待。
Mutex 对象
Mutex 与Monitor类似,这里不再累赘,需要注意的是Mutex分两种:一种是本地Mutex一种是系统级Mutex,系统级Mutex可以用来进行跨进程间的线程的同步。尽管 mutex 可以用于进程内的线程同步,但是使用 Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它可以更好地利用资源。相比之下,Mutex 类是 Win32 构造的包装。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
