线程基础
一个进程由若干个线程组成,线程是程序执行的基本原子单位。线程是"进程"中某个单一顺序的控制流,线程是进程中的一个基本执行流,每个线程都有自己专属的寄存器(程序计数器、栈指针等),代码共享区,不同的线程可以执行同样的方法。
多线程可以实现并行处理,可以避免某项任务长时间占用CPU时间,需要注意的是,多线程程序对于效率,应该根据任务不同的要求来选择。
线程的命名空间System.Threading
Thread类是线程中最重要的一个,Thread类提供了创建并控制线程,设置其优先级并获取其状态的方法。
Thread类的声明:
class ThreadSimple
{
//静态线程函数
public static void ThreadMethodExample()
{
}
}
//调用静态方法
Thread threadSimple = new Thread(ThreadSimple.ThreadMethodExample);
或:
class ThreadSimple
{
//静态线程函数
public static void ThreadMethodExample()
{
}
//调用静态方法
//Thread threadSimple = new Thread(ThreadSimple.ThreadMethodExample);
Thread threadSimple = new Thread(new ThreadStart(ThreadMethodExample));
}
-
线程启动、结束
Thread类的常用方法:
- IsAlive:判断线程是否处于活动状态
- Name:线程的名称
- Priority:ThreadPriority枚举类型,代表线程的优先级{Normal,AboveNormal,BelowNormal,Highest,Lowest}
- ThreadState:ThreadState枚举类型,代表线程的状态 { Running(线程已启动,正在执行) , StopRequested(正在请求停止此线程), SuspendRequested(正在请求挂起此线程), Background(线程正在被作为后台线程执行), Unstarted(尚未启动线程),Stopped(线程已经停止),WaitSleepJoin(线程已经被阻止),Suspended(线程已经挂起),AbortRequested(对线程调用了Thread.Abort()方法但是线程尚未收到试图终止它的挂起的System.Threading.ThreadAbortException) , Aborted(线程状态包括AbortRequested,并且该线程现在已死,但其状态尚未更改为Stopped) }
- Start:启动一个线程
- Suspend:挂起一个线程的运行(暂停、中断)
- Resume:继续被挂起的线程,恢复被Suspend()方法挂起的线程的执行
- Abort():结束一个线程的运行,终止线程
- Sleep():线程的休眠,使线程进入一定时间的休眠状态,时间一到,线程继续执行
-
线程间数据同步
-
线程间数据共享
多线程编程中,如果线程间需要共享数据,需要把共享的数据设置为静态类型的,此时可以使用static关键字
-
Lock语句同步数据访问
线程之间的同步和通信处理,两个线程需要同时操作一个队列,一个线程进行添加操作,另一个线程进行取用元素操作。
lock关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。Lock的语法:lock(expression)statement_block【其中expression是加锁对象,必须是引用类型,不能是数值类型,statement_block代表正在访问的共享资源的程序段】,
lock语句可以很好地实现互斥操作,从而保护数据在某个时刻内只有一个线程可以操作该数据,直至操作完成才允许其他线程进行操作,这样就实现了按顺序操作的设计,从而避免不可预料的情况发生。
-
public static void MethodSubB()
{
do
{
lock(lockExample)
{
i-=1;
Console.WriteLine("线程2开始,共享数据为:i={0}",i);
Thread.Sleep(2000); //线程A休眠2秒
Console.WriteLine("线程2结束,共享数据值i={0}",i);
}
} while (true);
}
线程可以使用Mutex.WaitOne()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只能等待。
Monitor类用于锁定对象,一个线程只有得到这把锁才能对该对象进行操作,对象锁保证了在可能引起混乱的情况下,一个时刻只有一个线程可以访问这个对象。
Monitor必须和一个具体的对象相关联,但由于它是一个静态类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。
当一个线程调用Monitor.Enter()方法锁定对象时,这个对象就归它所有了,其他线程想要访问这个对象,只有等待它调用Monitor.Exit()方法释放锁。
Monitor类主要成员
Enter |
在指定对象上获取排他锁 |
Exit |
释放指定对象上的排它锁 |
Pluse |
通知等待队列中的线程锁定对象状态的更改 |
PluseAll |
通知所有的等待线程对象状态的更改 |
TryEnter |
试图获取指定对象上的排他锁 |
Wait |
释放对象上的锁并阻止当前线程,直到它重新获取该锁 |
private static Object sObjectA = new Object();
private static Object sObjectB = new Object();
public static void DemoA()
{
if(Monitor.TryEnter(sObjectA,1000))
{
Thread.Sleep(1000);
if (Monitor.TryEnter(sObjectB, 2000))
{
Monitor.Exit(sObjectB);
}
else
{
Console.WriteLine("TryEnter SObjectB超时...");
}
Monitor.Exit(sObjectA);
}
Console.WriteLine("执行DemoA");
}
public static void DemoB()
{
if (Monitor.TryEnter(sObjectB, 1000))
{
Thread.Sleep(1000);
if (Monitor.TryEnter(sObjectA, 2000))
{
Monitor.Exit(sObjectA);
}
else
{
Console.WriteLine("TryEnter SObjectA超时...");
}
Monitor.Exit(sObjectB);
}
Console.WriteLine("执行DemoB");
}
static void Main(string[] args)
{
Thread threadA = new Thread(DemoA);
Thread threadB = new Thread(DemoB);
threadA.Start();
threadB.Start();
Thread.Sleep(4000);
Console.WriteLine("线程结束");
}
5.带参数线程
在不传递参数的情况下,可以使用ThreadStart代理来执行函数,如果要传递参数给执行函数,则可使用ParameterizedThreadStart代理来链接函数
Thread类的4个重载的构造函数
1.Thread(ThreadStart)[初始化Thread类的新实例]
2.Thread(ParameterizedThreadStart)
[初始化Thread类的新实例,指定允许对象在线程启动时传递给线程的委托]
3.Thread(ParameterizedThreadStart,Int32)
4.Thread(ThreadStart,Int32)
[初始化Thread类的新实例,并指定线程的最大堆栈]
实例:
class ThreadDemo
{
public int paraA, paraB;
public void MethodDemo()
{
Console.WriteLine("paraA={0},paraB={1}", paraA, paraB);
}
public void Print(object obj)
{
Console.WriteLine("传入的参数是{0}", obj.ToString());
}
}
static void Main(string[] args)
{
ThreadDemo A = new ThreadDemo();
A.paraA = 2;
A.paraB = 3;
Thread threadA = new Thread(new ThreadStart(A.MethodDemo));
threadA.Start();
Thread threadB = new Thread(new ParameterizedThreadStart(new ThreadDemo().Print));
threadB.Start(" 这是传入的参数");
Console.Write("Press any key to continue...");
Console.ReadKey();
}
6.线程池ThreadPool
线程池是可以在后台执行多个任务的线程集合,这使得主线程可以自由地异步执行其他任务。一旦池中的某个线程任务完成,它将返回到等待线程队列中等待在此被使用。线程池线程都是后台线程。
以下情况应该使用单独的线程不宜使用线程池
1.线程需要指定优先级
2.线程执行的时间较长
3.线程在单独的线程apartment中
4.在线程执行的过程中对线程存在操作
static void MethodA(object num)
{
int QueueNum = (int)num;
Console.WriteLine("线程号:{0}", QueueNum);
//输出空行,为了美观
Console.WriteLine();
}
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
//在线程池中创建线程池线程来执行指定的方法(用WaitCallBack来表示),并将此线程排入线程池的队列等待执行
ThreadPool.QueueUserWorkItem(new WaitCallback(MethodA), i);
}
}