c#多线程编程笔记
http://blog.csdn.net/davyfamer/archive/2007/02/08/1505446.aspx
[myElement]表示变量
第一部分 线程的概念
第二部分 线程的基本用法
第一步:引入命名空间:using System.Threading;
第二步:申明ThreadStart(即线程的入口);语法格式如下:
ThreadStart myThreadStart=new ThreadStart([method]);
第三步:定义一个线程;语法格式如下:
Thread [threadOne]=new Thread([ThreadStart]);
[ThreadStart]必须定义好!
第四步:执行线程,语法格式如下:
[Thread].Start();
其它语法:
挂起线程:表示处理器不再需要安排这个线程的执行。[workerThread].Suspend();
休眠进程:表示暂停[wokerThread].Sleep([time]);
联接线程:将使调用线程进入WaitSleepJoin状态,然后调用线程将阻塞,直到另一个线程实例终止。[worker].Join();
例子1:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;//引入这个命名空间
namespace ConsoleApplication1
{
class Program1
{
static void main(string[] args)
{
Console.WriteLine(”{Main Thread} Creating the thread start delegate.”);
ThreadStart workerThreadStart = new ThreadStart(SimpleWorkerThread);//定义一//个线程入口为SimpleWorkerThread.
Console.WriteLine(”{Main Thread} Creating the worker thread.”);
Thread workerThread = new Thread(workerThreadStart);//定义一个线程,线程名为workerThread
//线程开始
workerThread.Start();
Console.Read();
//线程的非正常结束
workerThread.Abort();
Console.WriteLine(”{Main Thread} Aborting worker thread.”);
workerThread.Join();
Console.WriteLine(”{Main Thread} Worker Thread Terminated.”);
Console.Read();
}
public static void SimpleWorkerThread()
{
for (int i = 0; ; i++)
{
try
{
Console.WriteLine(”Hello from the worker thread.”);
if (i > 1000)
{
Thread.CurrentThread.Abort();
return;
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString()+” Exception caught.”);
Thread.ResetAbort();
}
}
}
}
}
第三部分 线程的同步
同步的意思是在多线程程序中,为了使两个或多个线程之间,对分配临界资源的分配问题,要如何分配才能使临界资源在为某一线程使用的时候,其它线程不能再使用,这样可以有效地避免死锁与脏数据。脏数据是指两个线程同时使用某一数据,造成这个数据出现不可预知的状态!在C#中,对线程同步的处理有如下几种方法:
a) 等待事件:当某一事件发生后,再发生另一件事。
例子3:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class ClassCounter
{
protected int m_iCounter = 0;
public void Increment()
{
m_iCounter++;
}
public int Counter
{
get
{
return m_iCounter;
}
}
}
public class EventClass
{
protected ClassCounter m_protectedResource = new ClassCounter();
protected ManualResetEvent m_manualResetEvent = new ManualResetEvent(false);//ManualResetEvent(initialState),initialState如果为true,则将初始状态设置为终止;如果为false,则将初始状态设置为非终止。
protected void ThreadOneMethod()
{
m_manualResetEvent.WaitOne();//在这里是将入口为ThreadOneMethod的线程设为等待
m_protectedResource.Increment();
int iValue = m_protectedResource.Counter;
System.Console.WriteLine(”{Thread one} - Current value of counter:”+iValue.ToString());
}
protected void ThreadTwoMethod()
{
int iValue = m_protectedResource.Counter;
Console.WriteLine(”{Thread two}-current value of counter;”+iValue.ToString());
m_manualResetEvent.Set();//激活等待的线程
}
static void Main()
{
EventClass exampleClass = new EventClass();
Thread threadOne = new Thread(new ThreadStart(exampleClass.ThreadOneMethod));
Thread threadTwo = new Thread(new ThreadStart(exampleClass.ThreadTwoMethod));
threadOne.Start();//请注意这里,这里是先执行线程1
threadTwo.Start();//再执行线程2,那么线程2的值应该比线程1大,但结果相反
Console.ReadLine();
}
}
}
ManualResetEvent它允许线程之间互相发消息。
结果如下:
a) 使用Mutex类
Mutex是一个特殊的同步类,只能用来同步线程,不过Mutex可以跨进程对线程进行同步。Mutex类确保了一次只有一个线程可以访问同一资源。MSDN中的描述如下:“ Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。”对互斥体,MSDN有如下描述:“互斥体有两种类型:局部互斥体和已命名的系统互斥体。如果使用接受名称的构造函数创建 Mutex 对象,则该对象与具有该名称的操作系统对象关联。已命名的系统互斥体在整个操作系统中都可见,可用于同步进程活动。您可以创建多个 Mutex 对象来表示同一个已命名的系统互斥体,也可以使用 OpenExisting 方法打开现有的已命名系统互斥体。局部互斥体仅存在于您的进程内。您的进程中任何引用局部 Mutex 对象的线程都可以使用它。每个 Mutex 对象都是一个单独的局部互斥体。”
例子4:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class mutex4
{
private static Mutex mut=new Mutex();
private const int numIterations = 1;
private const int numTreads = 3;
static void Main()
{
Thread myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = “Thread”;
Thread myThread1 = new Thread(new ThreadStart(MyThreadProc));
myThread1.Name = “Thread1″;
Thread myThread2 = new Thread(new ThreadStart(MyThreadProc));
myThread2.Name = “Thread2″;
myThread.Start();
myThread1.Start();
myThread2.Start();
}
private static void MyThreadProc()
{
for (int i = 0; i < numIterations; i++)
{
UseResource();
}
}
private static void UseResource()
{
//mut.WaitOne();
Console.WriteLine(”{0} has entered the protected area”,Thread.CurrentThread.Name);
Thread.Sleep(500);
Console.WriteLine(”{0} is leaving the protected area\r\n”,Thread.CurrentThread.Name);
//mut.ReleaseMutex();
}
}
}
这个程序没有加入mut同步机制,运行结果如下:
如果我们把mut.WaitOne()与mut.ReleaseMutex()前的//去掉,运行结果如下:
可以看出,这样线程就完全按我们运行的顺序运行了!
在这里值得一提的是Mutex.WaitOne有三种重载方法:Mutex.WaitOne();Mutex.WaitOne(int [millisecondTimeOut],bool [exitContext]),Mutex.WaitOne(TimeSpan [timeout],bool [exitContext])
其中第一参数是指超时的时间限止,第二个参数指定了线程在重新获取同步上下文之前是否需要先退出同步。
例子5:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class mutexWaitOne5
{
static AutoResetEvent autoEvent = new AutoResetEvent(false); //AutoResetEvent 类表示一个本地等待处理事件,在释放了单个等待线程以后,该事件会在终止时自动重置。而ManualResetEvent在释放了单个等待线程之后只能手动重置
static void Main()
{
Console.WriteLine(”Main starting.”);
ThreadPool.QueueUserWorkItem(new WaitCallback(WorkMethod),autoEvent);//Thread提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。WaitCallback([Method])表示线程池要回调的方法。autoEvent是传入WorkMethod的参数。
if (autoEvent.WaitOne(1000, false))
{
Console.WriteLine(”Work method signaled.”);
}
else
{
Console.WriteLine(”Timed out waiting for work method to signal.”);
}
Console.WriteLine(”Main ending.”);
}
static void WorkMethod(object stateInfo)
{
Console.WriteLine(”Work starging.”);
int i = new Random().Next(100,2000);
Console.WriteLine(”the value of random is {0}”,i);
Thread.Sleep(i);
Console.WriteLine(”Work ending.”);
((AutoResetEvent)stateInfo).Set() ;//将事件状态设为终止态,允许一个或多个等待线程继续
}
}
}
当随机数大于1000时,线程占有互斥体的时间超过了设定值1000,会出现如下结果:
当随机数小于1000时,线程占有互斥体的时间未超过设定值1000,会出现如下结果:
a) 使用Monitor类
Monitor类提供了锁定部分代码的简单机制,只要把受保护的代码包装在Monitor.Enter与Monitor.Exit代码块中就行了。Monitor.Enter方法与Monitor.Exit方法都有一个参数。
Monitor.Enter(object [obj]);Monitor.Exit(object [obj]).这个参数就是需要Monitor锁定的对象,它应该是一个引用类型,而不是值类型。
Monitor具有以下功能:(摘自MSDN)
它根据需要与某个对象相关联。
它是未绑定的,也就是说可以直接从任何上下文调用它。
不能创建Monitor类的实例。
例子6:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class MonitorClass6
{
protected static int m_iCounter = 0;
public void Increment()
{
m_iCounter++;
}
public int Counter
{
get
{
return m_iCounter;
}
}
}
public class test
{
public MonitorClass6 mc6 = new MonitorClass6();
public void increseMC6()
{
Monitor.Enter(mc6);//进入临界区
try
{
mc6.Increment();
Console.WriteLine(”线程{0}的值为{1}”,Thread.CurrentThread.Name,mc6.Counter);
}
finally
{
Monitor.Exit(mc6);//退出临界区
}
}
}
public class MainEntryPoint
{
public static test myTest = new test();
public static void Main()
{
ThreadStart ts1 = new ThreadStart(execute);
Thread t1 = new Thread(ts1);
t1.Name = “Thread1″;
ThreadStart ts2 = new ThreadStart(execute1);
Thread t2 = new Thread(ts2);
t2.Name = “Thread2″;
t1.Start();
t2.Start();
Console.Read();
}
public static void execute()
{
for (int i = 0; i <5; i++)
{
myTest.increseMC6();
}
}
public static void execute1()
{
for (int i = 0; i < 5; i++)
{
myTest.increseMC6();
}
}
}
}
执行结果如下:
可以看到它没有经过顺序上的紊乱就达到10;如果我们把两个有带有Monitor注释掉,结果会变成下图所示(您机子上的结果也许顺序不一样):
可以看出,由于我们没有对临界资源加以控制,而形成了我们不需要的“脏”数据!
也许细心的你会发现这个Monitor类的形式与使用LOCK关键字基本一致,但我认为它比lock关键字强大一些,因为Monitor类还具有 TryEnter(试图获取指定对象的排他锁)、Wait(释放对象上的锁并阻止当前线程)、Pulse(能知等待队列中的线程锁定对象状态的更改)以及PulseAll(通知所有的等待线程的对象状态已改变)等方法。
TtyEnter
其方法的重载:
Monitor.TryEnter(Object):试图获得指定对象的排他锁
Monitor.TryEnter(Object,Int32):在指定的毫秒数内尝试获取指定对象上的排他锁
Monitor.TryEnter(Object,TimeSpan):在指定的时间量内尝试获取指定对象上的排他锁
将例子6的increseMC6方法改为如下:
public void increseMC6()
{
if (Monitor.TryEnter(mc6))//进入临界区
{
try
{
mc6.Increment();
Console.WriteLine(”线程{0}的值为{1}”, Thread.CurrentThread.Name, mc6.Counter);
}
finally
{
Monitor.Exit(mc6);//退出临界区
}
}
else
{
Console.WriteLine(”线程{0}没有竞得资源”,Thread.CurrentThread.Name);
}
}
}
Wait
其方法的重载:
Monitor.Wait(Object):释放对象上的锁并阻止当前线程,直到它重新获取该锁
Monitor.Wait(Object,Int32):释放对象上的锁并阻止当前线程,直到它重新获取该锁。如果指定的超时间隔已过,则线程进入就绪队列。
Monitor.Wait(Object,TimeSpan):同上。
Monitor.Wait(Object,Int32,Boolean):前两个与第二个一样。其中的Boolean表示是否在等待之前退出上下文的同步域然后重新获取该同步域。
Monitor.Wait(Object,TimeSpan,Boolean):同上。
例程7:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class Monitor7
{
protected int m_iCounter = 0;
public void Increment()
{
m_iCounter++;
}
public int Counter
{
get
{
return m_iCounter;
}
}
}
public class MonitorPulseClass
{
protected Monitor7 m_protectedResource = new Monitor7();
protected void ThreadOneMethod()
{
for (int i = 0; i < 5; i++)
{
lock (m_protectedResource)
{
Console.WriteLine(”进入第一个线程,I的值为:{0}”,i);
Monitor.Wait(m_protectedResource);
m_protectedResource.Increment();
int iValue = m_protectedResource.Counter;
Console.WriteLine(”{Thread One} - Current value of counter:” + iValue.ToString());
}
}
}
protected void ThreadTwoMethod()
{
for (int i = 0; i < 5; i++)
{
lock (m_protectedResource)
{
Console.WriteLine(”进入第二个线程,I的值为:{0}”,i);
int iValue = m_protectedResource.Counter;
Console.WriteLine(”{Thread Two} - Current value of counter” + iValue.ToString());
Monitor.PulseAll(m_protectedResource);
}
}
}
static void Main()
{
MonitorPulseClass exampleClass = new MonitorPulseClass();
Thread threadOne = new Thread(new ThreadStart(exampleClass.ThreadOneMethod));
Thread threadTwo = new Thread(new ThreadStart(exampleClass.ThreadTwoMethod));
threadOne.Start();
threadTwo.Start();
System.Console.Read();
}
}
}
执行结果如下:由此可以得到流程如下:首先进入thread1然后被Monitor.Wait中断,从而进入thread2,在thread2中结束之后,通过Monitor.PulseAll通知thread1并执行,但又被Monitor.wait中断退出。
a) 使用InterLocked类
InterLocked使用于递增、递减以及更改变量值这类较为简单的操作。如果所有的任务都是在同步上下文中的一些简单操作,那么InterLocked类作为一个非常便捷的方法,可以大大减少需要编写的代码量。笔者没有在如下的试例程序中没有感觉到它的功用,当然 也不排除笔者技术有限未能理解到设计者的心思。
例子8:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class InterLocked8
{
public static int i =0;
public void Method1()
{
if (i < 20)
{
Interlocked.Increment(ref i);
}
else
{
Interlocked.Decrement(ref i);
}
Thread.Sleep(500);
Console.WriteLine(”Current thread is {0},the value of i is {1}”, Thread.CurrentThread.Name, i);
}
}
class MainEntryPoint1
{
public static void Main()
{
Thread myThread;
InterLocked8 il8 = new InterLocked8();
for (int n = 0; n < 20; n++)
{
myThread = new Thread(new ThreadStart(il8.Method1));
myThread.Name = String.Format(”Thread{0}”, n);
myThread.Start();
}
Console.ReadLine();
}
}
}
b) 无等待读取
当一个线程处在更改变量值的过程中,另一个线程也要更改该变量的值或需要读取变量值,就会出现同步的问题,前文中已介绍了这些同步技术都用当一个线程执行受保护的代码部分时,就会阻塞其它线程对这部分的操作。但如果所有的线程都只读取这个资源,而不改变它的时候,这样做其实是没有必要与浪费时间的。在。NET中用无等待读取来提供这方面的功能。
例子9:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class ReaderWriterClass
{
protected ReaderWriterLock m_readerLock = new ReaderWriterLock();
protected int m_counter = 0;
protected int m_readerBlocks = 0;
protected int m_writerBlocks = 0;
protected void ThreadOneMethod()
{
for (int i = 0; i < 200; i++)
{
try
{
m_readerLock.AcquireReaderLock(0);
try
{
System.Console.WriteLine(m_counter);
}
finally
{
m_readerLock.ReleaseReaderLock();
}
}
catch (Exception )
{
Interlocked.Increment(ref m_readerBlocks);
}
}
}
protected void ThreadTwoMethod()
{
for (int i = 0; i < 100; i++)
{
try
{
m_readerLock.AcquireWriterLock(0);
try
{
Interlocked.Increment(ref m_counter);
}
finally
{
m_readerLock.ReleaseWriterLock();
}
}
catch (Exception)
{
Interlocked.Increment(ref m_writerBlocks);
}
Thread.Sleep(1);
}
}
public int ReaderBlocks
{
get
{
return m_readerBlocks;
}
}
public int WriteerBlocks
{
get
{
return m_writerBlocks;
}
}
static void Main()
{
ReaderWriterClass exampleClass = new ReaderWriterClass();
Thread threadOne = new Thread(new ThreadStart(exampleClass.ThreadOneMethod));
Thread threadTwo=new Thread(new ThreadStart(exampleClass.ThreadTwoMethod));
threadOne.Start();
threadTwo.Start();
threadOne.Join();
threadTwo.Join();
System.Console.WriteLine(”Reader Blocks {0},writer blocks {1}”,exampleClass.ReaderBlocks,exampleClass.WriteerBlocks);
System.Console.Read();
}
}
}
第四部分 对非同步线程使用线程池
线程池是可以用来在后台执行多个任务的线程集合。它的提出主要是因为有很多线程是在某一事件被触发之后才发生的,在这一事件发生之前这是处于休眠或者是等待状态,而在它的触发事件之后,它可以得到执行,执行完成以后,又进入休眠状态。
a) WaitCallBack
WaitCallBack表示线程池要执行的回调方法。语法如下:
WaitCallBack [MyCallBack]=new WaitCallBack([ThreadPoolWorkerThreadMethod]);
它的语法与ThreadStart差不多,但它委托的事件有一参数,它委托的事件的申明如下:
例子10:
Static void ThreadPoolWorkerThreadMethod(Object[stateObject])
{
….
}
[stateObject]是一个状态对象,能够将信息传递给辅助线程。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class Class1
{
public static void Main()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));
Console.WriteLine(”Main thread does some work,then sleeps.”);
Thread.Sleep(1000);
Console.WriteLine(”Main Thread Exit.”);
Console.Read();
}
public static void ThreadProc(object stateInfo)
{
Console.WriteLine(”Hello from the thread pool!”);
}
}
}
b) 将工作项排入队列
要使用线程池,就要先要调用ThreadPool.QueueUserWorkItem方法将工作项加入队列。语法:
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolThreadMethod));
例程11:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class QueueUserWorkerItem1
{
static void Main()
{
Console.WriteLine(”{Main Thread} Queing the work item.”);
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolThreadMethod));
Console.WriteLine(”{Main Thread} Press the ‘Enter’ key t exit the process.”);
Console.ReadLine();
Console.WriteLine(”{Main Thread} Exiting the process.”);
}
static void ThreadPoolThreadMethod(Object stateObject)
{
Console.WriteLine(”{Thread Pool} Hello Thread Pool.”);
Console.ReadLine();
}
}
}
c) 向线程传递数据
ThreadPool构造函数使用一个WaitCallback委托作为参数,利用这个参数可以向ThreadPool传递任意状态或信息,从而传递给线程方法。
例程12:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class PassAWord10
{
static void Main()
{
Console.WriteLine(”{Main Thread} Queuing the work item.”);
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolThreadMethod),”This is a state message”);
Console.WriteLine(”{Main Thread} Press the ‘Enter’ Key to exit the process.”);
Console.Read();
}
static void ThreadPoolThreadMethod(Object stateObject)
{
Console.WriteLine(”{Thread Pool} The data passed in is ‘”+stateObject.ToString()+”‘”);
}
}
}
)告知,即刻删除。