每个正在操作系统中运行的应用程序都是一个进程,一个进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元。进程就好像是一个公司,公司中的每个员工就相当于线程,公司想要运行就必须得有负责人,负责人就相当于主线程。
单线程
单线程就是只有一个线程。默认情况下,系统为应用程序分配一个主线程,该线程执行程序中以Main方法开始和结束的代码。
多线程
需要用户交互的软件都必须尽可能的对用户的活动做出反应,以便提供更丰富的用户体验。但同时它又必须执行必要的计算,以便尽可能快的将数据呈现给用户,这时就要使用多线程。
优点:要提供对用户的响应速度并且处理所需数据,以便同时完成工作,使用多线程是一种强大的技术。多线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。
Thread类
Thread类位于System.Threading命名空间下,System.Threading命名空间提供一些可以进行多线程编程的类和接口。Thread类主要用于创建并控制线程、设置线程优先级并获取其状态。
Thread类的常用属性及说明属性说明ApartmentState获取或设置该线程的单元状态CurrentContext获取线程正在其中执行的当前上下文CurrentThread获取当前线程正在运行的线程IsAlive获取一个值,该值指示当前线程的执行状态ManagedThreadld获取当前托管线程的唯一标识符Name获取或设置线程的名称Priority获取或设置一个值,改制指示线程的调度优先级ThreadState获取一个值,该值包含当前线程的状态Thread类的常用属性及说明方法说明Abort在调用该方法的线程上引发ThreadAbortException,以开始终止该线程的过程。调用该方法通常会终止线程GetApartmentState返回一个ApartmentState值,该值指示单元状态GetDomain返回当前线程正在其中运行的当前域GetDomainID返回唯一的应用程序标识符Interrupt中断处于WaitSleepJoin线程状态的线程Join阻止调用线程,直到某个线程终止时为止ResetAbort取消为当前线程请求的AbortResume继续已挂起的线程SetpartmentState在线程启动前设置其单元状态Sleep将当前线程阻止指定的毫秒数SpainWait导致线程等待由iterations参数定义的时间量Start使线程被安排进行执行Suapent挂起线程,或者如果线程已挂起,则不起作用VolatileRead读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值VolatileWrite立即向字段写入一个值,一边该值对计算机中的所有处理器都可见
演示使用Thread类的相关方法:
namespace Thread3
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
string strInfo = string.Empty;//定义一个空字符串,用来记录线程相关信息
Thread myThread = new Thread(new ThreadStart(threadOut));//实例化Thread线程类对象
myThread.Start(); //启动主线程
//获取线程相关信息
strInfo += "线程唯一标识符:" + myThread.ManagedThreadId;
strInfo += "\n线程名称:" + myThread.Name;
strInfo += "\n线程状态:" + myThread.ThreadState.ToString();
strInfo += "\n线程优先级;" + myThread.Priority.ToString();
strInfo += "\n是否为后台线程:" + myThread.IsBackground;
Thread.Sleep(1000); //使主线程休眠1秒
myThread.Abort("退出"); //通过主线程阻止新开线程
myThread.Join(); //等待新开线程结束
MessageBox.Show("线程运行结束");
richTextBox1.Text = strInfo;
}
public void threadOut()
{
MessageBox.Show("主线程开始运行");
}
}
}
运行结果:
创建线程
创建一个线程非常简单,只需要将其声明并为其提供线程起始点处的委托即可。创建新的线程时,需要使用Thread类,该类具有接受一个ThreadStart委托或ParameterizedThreadStart委托的构造函数。
线程中为什么要使用委托ThreatStart:http://www.cnblogs.com/lvdongjie/p/5469274.html
ThreadStart:用于无返回值、无参数的方法
namespace Thread1
{
class Program
{
static void Main(string[] args)
{
//创建ThreadStart委托实例
ThreadStart TS = new ThreadStart(MyThread);
Console.WriteLine("Create the main thread");
//创建Thread类的实例
Thread thread = new Thread(TS);
thread.Start();
}
//线程函数
static void MyThread()
{
Console.WriteLine("Child start Thread...");
Thread.Sleep(1000);
Console.WriteLine("Thread is over...");
}
}
}
运行结果:
ParameterizedThreadStart:用于带参数的方法
namespace Thread1
{
class Program
{
static void Main(string[] args)
{
//创建线程委托实例
ParameterizedThreadStart pts = new ParameterizedThreadStart(MyThread);
Console.WriteLine(" Creating the Child thread");
//创建线程对象
Thread thread = new Thread(pts);
thread.Start(10);
Console.ReadKey();
}
private static void MyThread(object n)
{
Console.WriteLine("Thread started ...");
for(int i = 0;i <= (int)n; i+=2)
{
Console.WriteLine(i);
}
}
}
}
运行结果:
线程的挂起和恢复
创建完一个线程并启动后,还可以挂起、恢复、休眠或终止它。线程的挂起与恢复可以通过调用Thread类的Suspend方法和Resume方法实现。
- Suspend方法
public void Suspend();
该方法用来挂起线程,如果线程已挂起,则不起作用。调用Suspend方法挂起线程时,.NET允许挂起的线程再执行几个指令,目的是为了到达.NET认为线程可以安全挂起的状态。
- Resume方法
public void Resume();
该方法用来继续已挂起的线程。通过Resume方法来恢复被暂停的线程时,无论调用多少次Suspend方法,调用Resume方法均会使另一个线程脱离挂起状态。
namespace Thread1
{
class Program
{
static void Main(string[] args)
{
Thread myThread; //声明线程
//用线程起始点的ThreadStart委托创建该线程的实例
myThread = new Thread(new ThreadStart(createThread));
myThread.Start(); //启动线程
myThread.Suspend(); //挂起线程
myThread.Resume(); //恢复挂起的线程
}
private static void createThread()
{
Console.WriteLine("创建线程");
}
}
}
线程休眠
线程休眠主要通过Thread类的Sleep方法实现。该方法用来将线程阻止指定的时间。
namespace Thread4
{
class Program
{
static void Main(string[] args)
{
ThreadStart ts = new ThreadStart(ChildThread);//创建一个线程的委托
Console.WriteLine("创建子线程");
Thread thread = new Thread(ts);
thread.Start();
Console.ReadKey();
}
public static void ChildThread()
{
Console.WriteLine("子线程启动");
int SleepTime = 2000;
Console.WriteLine("子线程休眠{0}秒", SleepTime / 1000);
Thread.Sleep(SleepTime);
Console.WriteLine("子线程恢复");
}
}
}
运行结果:
终止线程
- Abort方法
Abort方法用来终止线程,它有两种重载形式:
(1)终止线程,在调用该方法的线程上引发ThreadAbortException异常,以开始终止该线程: public void Abort();
(2)终止线程,在调用该方法的线程上引发ThreadAbortException异常,以开始终止该线程并提供有关线程终止的异常信息的过程:
public void Abort(Object stateInfo)
namespace Thread5
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("主线程启动");
//创建一个线程委托实例
ThreadStart ts = new ThreadStart(ChildThread);
Console.WriteLine("创建子线程");
Thread thread = new Thread(ts);//创建线程对象
thread.Start();//启动线程
Thread.Sleep(2000);//主线程休眠
Console.WriteLine("终止子线程");
thread.Abort();//线程终止
Console.ReadKey();
}
public static void ChildThread()
{
try
{
Console.WriteLine("子线程启动");
//打印20以内的偶数
for (int i = 0; i <= 20; i += 2)
{
Thread.Sleep(500);//线程休眠1s
Console.WriteLine(i);
}
Console.WriteLine("子线程完成");
}
catch
{
Console.WriteLine("线程中止异常");
}
finally
{
Console.WriteLine("捕获异常信息");
}
}
}
}
运行结果:
- Join 方法
Join()
在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止。
Join(int 32)
在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止或经过了指定时间为止
Join(TimeSpan)
在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止或经过了指定时间为止。
Join():
public class Example
{
static Thread thread1, thread2;
public static void Main()
{
thread1 = new Thread(ThreadProc);
thread1.Name = "Thread1";
thread1.Start();
thread2 = new Thread(ThreadProc);
thread2.Name = "Thread2";
thread2.Start();
}
private static void ThreadProc()
{
Console.WriteLine("\nCurrent thread: {0}", Thread.CurrentThread.Name);
if (Thread.CurrentThread.Name == "Thread1" &&
thread2.ThreadState != ThreadState.Unstarted)
thread2.Join();
Thread.Sleep(4000);
Console.WriteLine("\nCurrent thread: {0}", Thread.CurrentThread.Name);
Console.WriteLine("Thread1: {0}", thread1.ThreadState);
Console.WriteLine("Thread2: {0}\n", thread2.ThreadState);
}
}
运行结果:
建议去官网看对join()方法的详细解释:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.thread.join?view=netframework-4.8
线程优先级
线程的优先级指定一个线程相对于另一个线程的相对优先级。每个线程都有一个分配的优先级。线程是根据其优先级而调度执行的。
线程的优先级值及说明优先级值说明AboveNormal可以将Thread安排在具有Highest优先级的线程之后,在具有Normal优先级的线程之前BelowNormal可以将Thread安排在具有Normal优先级的线程之后,在具有Lowest优先级的线程之前Highest可以将Thread安排在任何其他优先级的线程之前Lowest可以将Thread安排在任何其他优先级的线程之后Normal可以将Thread安排在具有AboveNormal优先级的线程之后,在具有Below Normal优先级的线程之前。默认情况下,线程具有Normal优先级
//线程优先级
namespace Thread2
{
class Program
{
static void Main(string[] args)
{
//因为下面要用到program类中的非静态函数,所以先创建该类对象
Program program = new Program();
//创建线程委托1
ThreadStart ts1 = new ThreadStart(program.even);
Console.WriteLine("In Main: Creating the thread1 thread.");
//创建线程1的实例
Thread thread1 = new Thread(ts1);
//设置打印偶数优先级为最低
thread1.Priority = ThreadPriority.Lowest;
//创建线程委托2
ThreadStart ts2 = new ThreadStart(program.odd);
Console.WriteLine("In Main: Creating the thread2 thread.");
//创建线程2的实例
Thread thread2 = new Thread(ts2);
//设置打印奇数优先级为最高
thread2.Priority = ThreadPriority.Highest;
thread1.Start();//偶数 低
thread2.Start();//奇数 高
Console.ReadKey();
}
//打印奇数
public void odd()
{
Console.WriteLine("List of odd numbers: ");
for (int i = 1; i < 100; i += 2)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
//打印偶数
public void even()
{
Console.WriteLine("List of even numbers: ");
for (int i = 0; i < 100; i += 2)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
}
}
运行结果:
第一次:
第二次:
第三次:
从上面的运行效果可以看出,由于输岀奇数的线程的优先级高于输出偶数的线程,所以在输出结果中优先输出奇数的次数会更多。
线程同步
lock:
lock关键字可以用来确保代码块完整运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现。
using System;
using System.Threading;
//线程优先级
namespace Thread2
{
class Program
{
static void Main(string[] args)
{
//因为下面要用到program类中的非静态函数,所以先创建该类对象
Program program = new Program();
//创建线程委托1
ThreadStart ts1 = new ThreadStart(program.even);
Console.WriteLine("In Main: Creating the thread1 thread.");
//创建线程1的实例
Thread thread1 = new Thread(ts1);
//设置打印偶数优先级为最低
thread1.Priority = ThreadPriority.Lowest;
//创建线程委托2
ThreadStart ts2 = new ThreadStart(program.odd);
Console.WriteLine("In Main: Creating the thread2 thread.");
//创建线程2的实例
Thread thread2 = new Thread(ts2);
//设置打印奇数优先级为最高
thread2.Priority = ThreadPriority.Highest;
thread1.Start();//偶数 低
thread2.Start();//奇数 高
Console.ReadKey();
}
//打印奇数
public void odd()
{
lock (this)
{
Console.WriteLine("List of odd numbers: ");
for (int i = 1; i < 100; i += 2)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
}
//打印偶数
public void even()
{
lock (this)
{
Console.WriteLine("List of even numbers: ");
for (int i = 0; i < 100; i += 2)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
}
}
}
运行结果:
Monitor:
Monitor类提供了同步对象的访问机制,他通过向单个线程授予对象锁来控制对象的访问,对象锁提供限制访问代码块的能力。当一个线程拥有对象锁时,其他线程都不能获取该锁。
Monitor类的主要功能:
- 它根据需要与某个对象相关联
- 他是未绑定的,可以直接从任何上下文调用它
- 不能创建Monitor类的实例
Monitor类的常用方法及说明方法说明Enter在指定对象上获取排他锁Exit释放指定对象上的排他锁Pulse通知等待队列上的线程锁定对象状态的更改PulseAll通过所有的等待线程对象状态的更改TryEnter试图获取指定对象的排他锁Wait释放对象上的锁并阻止当前线程,直到它重新获取该锁
```
namespace Thread4
{
class Program
{
static void Main(string[] args)
{
Program myProgarm = new Program();//实例化对象
myProgarm.LockThread(); //调用锁定线程方法
}
void LockThread()
{
Monitor.Enter(this); //锁定当前线程
Console.WriteLine("锁定当前线程以实现线程同步");
Monitor.Exit(this); //释放当前线程
}
}
}
### Mutex:
当两个或更多线程同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。Mutex类是同步基元,他向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,知道第一个线程释放该互斥体。
Mutex类的常用方法及说明方法说明Close在派生类中被重写时,释放当前Waithandle持有的所有资源OpenExisting打开现有的已命名互斥体ReleaseMutex释放Mutex一次SignalAndWait原子操作的形式,向一个WaitHandle发出信号并等待另一个WaitAll等待指定数组中的所有元素都收到信号WaitAny等待指定数组中的任一元素收到信号WaitOne当在派生类中重写时,阻止当前线程,直到当前的WaitHandle收到信号
namespace Thread4
{
class Program
{
static void Main(string[] args)
{
Program myProgarm = new Program();//实例化对象
myProgarm.LockThread(); //调用锁定线程方法
}
void LockThread()
{
Mutex myMutex = new Mutex(false); //实例化mutex类对象
myMutex.WaitOne(); //阻止当前线程
Console.WriteLine("锁定线程以实现线程同步");
myMutex.ReleaseMutex(); //释放Mutex类
}
}
}
```
版权归原作者 互联网底层民工 所有, 如有侵权,请联系我们删除。