C#的线程技术及操作(线程优先级、线程同步)
目录
一、线程的优先级
????????线程的优先级指定一个线程相对于另一个线程的相对优先级。每个线程都有一个分配的优先级。在公共语言运行库内创建的线程最初被分配为Normal优先级,而在公共语言运行库外创建的线程,在进入公共语言运行库时将保留其先前的优先级。
- 线程是根据其优先级而调度执行的,用于确定线程执行顺序的调度算法随操作系统的不同而不同。
- 在某些操作系统下,具有最高优先级(相对于可执行线程而言)的线程经过调度后总是首先运行。
- 如果具有相同优先级的多个线程都可用,则程序将遍历处于该优先级的线程,并为每个线程提供一个固定的时间片段来执行。
- 只要具有较高优先级的线程可以运行,具有较低优先级的线程就不会执行。
- 如果在给定的优先级上不再有可运行的线程,则程序将移到下一个较低的优先级并在该优先级上调度线程以执行;
- 如果具有较高优先级的线程可以运行,则具有较低优先级的线程将被抢先,并允许具有较高优先级的线程再次执行(高优先级的可运行线程会抢占低优先级线程)。
- 除此之外,当应用程序的用户界面在前台和后台之间移动时,操作系统还可以动态调整线程的优先级。
- 一个线程的优先级不影响该线程的状态,该线程的状态在操作系统可以调度该线程之前必须为Running。
????????线程的优先级值及说明:
优 先 级 值 | 说 ???明 |
AboveNormal | 可以将Thread安排在具有Highest优先级的线程之后,在具有Normal优先级的线程之前 |
BelowNormal | 可以将Thread安排在具有Normal优先级的线程之后,在具有Lowest优先级的线程之前 |
Highest | 可以将Thread安排在具有任何其他优先级的线程之前 |
Lowest | 可以将Thread安排在具有任何其他优先级的线程之后 |
Normal | 可以将Thread安排在具有AboveNormal优先级的线程之后,在具有BelowNormal优先级的线程之前。默认情况下,线程具有Normal优先级 |
?
// 创建两个Thread线程类对象,并设置第一个Thread类对象
// 的优先级为最低,然后调用Start()方法开启这两个线程
namespace _05
{
class Program
{
/// <summary>
/// 使用自定义方法Thread1声明线程
/// 使用自定义方法Thread2声明线程
/// 设置线程的调度优先级
/// 这个例子并不严谨,输出结果不稳定,线程1和2是先后启动,
/// 操作系统并不能稳定地区分和处置优先级,
/// 或者即使能区分优先级但是来不及处置。
/// 一切,都是因为代码编写的太简单了。
/// </summary>
static void Main(string[] args)
{
Thread thread1 = new(new ThreadStart(Thread1))
{
Priority = ThreadPriority.Lowest
};
Thread thread2 = new(new ThreadStart(Thread2));
thread1.Start(); //开启线程一
//Console.WriteLine("测试线程一");
thread2.Start(); //开启线程二
//Console.WriteLine("测试线程二");
Console.Read();
}
static void Thread1()
{
Console.WriteLine("线程一");
}
static void Thread2()
{
Console.WriteLine("线程二");
}
}
}/*
线程二
线程一 */
二、线程同步
????????使用多个线程的一个好处是每个线程都可以异步执行。
- 对于Windows应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应。
- 对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力;否则,在完全满足前一个请求之前,将无法处理每个新请求。
- 线程的异步性意味着必须协调对资源(如文件句柄、网络连接和内存)的访问;否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作,结果将产生不可预知的数据损坏。
- 线程同步是指并发线程高效、有序地访问共享资源所采用的技术。所谓同步,是指某一时刻只有一个线程可以访问资源,只有当资源所有者主动放弃了代码或资源的所有权时,其他线程才可以使用这些资源。
- 线程同步可以分别使用C#中的lock关键字、Monitor类和Mutex类实现。
1.使用lock关键字实现线程同步
? ? ? ? lock关键字可以用来确保代码块完成运行,而不会被其他线程中断,它是通过在代码块运行期间为给定对象获取互斥锁来实现的。
????????lock语句以关键字lock开头,它有一个作为参数的对象,在该参数的后面还有一个一次只能由一个线程执行的代码块。lock语句语法格式如下。
Object thisLock =new Object();
lock (thisLock)
{
//要运行的代码块
}?
????????提供给lock语句的参数必须为基于引用类型的对象,该对象用来定义锁的范围。
????????事实上lock语句是用Monitor类来实现的,它等效于try/finally语句块,使用lock关键字通常比直接使用Monitor类更可取,一方面是因为lock更简洁;另一方面是因为lock确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过finally关键字来实现的,无论是否引发异常它都执行关联的代码块。
// 自定义一个LockThread()方法,该方法中使用lock关键字
// 锁定当前线程,然后在Main()方法中通过Program的类对象调用LockThread()自定义方法
namespace _06
{
class Program
{
int num = 10;//设置当前总票数
/// <summary>
/// Sleep()设置不同值,影响每次启动显示的效果,
/// 一般数值越小,随机性越大,数值越大,显示结果越稳定
/// </summary>
void Ticket()
{
while (true) //设置无限循环
{
lock (this) //锁定代码块,以便线程同步
{
if (num > 0) //判断当前票数是否大于0
{
Thread.Sleep(300); //使当前线程休眠100毫秒
Console.WriteLine(Thread.CurrentThread.Name + "----票数" + num--); //票数减1
}
}
}
}
/// <summary>
/// 创建对象,以便调用对象方法
/// 分别实例化4个线程,并设置名称
/// </summary>
static void Main(string[] args)
{
Program p = new();
Thread tA = new(new ThreadStart(p.Ticket))
{
Name = "线程一"
};
Thread tB = new(new ThreadStart(p.Ticket))
{
Name = "线程二"
};
Thread tC = new(new ThreadStart(p.Ticket))
{
Name = "线程三"
};
Thread tD = new(new ThreadStart(p.Ticket))
{
Name = "线程四"
};
tA.Start(); //分别启动线程
tB.Start();
tC.Start();
tD.Start();
Console.ReadLine();
}
}
}
???????2.使用Monitor类实现线程同步
????????Monitor类提供了同步对象的访问机制,它通过向单个线程授予对象锁来控制对对象的访问,对象锁提供限制访问代码块(通常称为临界区)的能力。当一个线程拥有对象锁时,其他任何线程都不能获取该锁。
????????Monitor类的主要功能如下。
- 它根据需要与某个对象相关联。
- 它是未绑定的,也就是说可以直接从任何上下文调用它。
- 不能创建Monitor类的实例。
- 使用Monitor类锁定的是对象(即引用类型)而不是值类型。
????????Monitor类的常用方法及说明
方 ??法 | 说 ??明 |
Ente | 在指定对象上获取排他锁 |
Exit | 释放指定对象上的排他锁 |
Puls | 通知等待队列中的线程锁定对象状态的更改 |
PulseAll | 通知所有的等待线程对象状态的更改 |
TryEnter | 试图获取指定对象的排他锁 |
Wait | 释放对象上的锁并阻止当前的线程,直到它重新获取该锁 |
?
// 自定义一个LockThread()方法,该方法中首先使用
// Monitor类的Enter()方法锁定当前的线程。
// 然后再调用Monitor类的Exit()方法释放当前的线程。
// 最后在Main()方法中通过Program的类对象调用LockThread()自定义方法
namespace _07
{
class Program
{
int num = 10;//设置当前总票数
/// <summary>
/// 创建对象,以便调用对象方法
/// 分别实例化4个线程,并设置名称
/// 分别启动线程
/// 这个代码仍然不能完全可控,结果仍然随机
/// </summary>
static void Main(string[] args)
{
Program p = new();
Thread tA = new(new ThreadStart(p.Ticket))
{
Name = "线程一"
};
Thread tB = new(new ThreadStart(p.Ticket))
{
Name = "线程二"
};
Thread tC = new(new ThreadStart(p.Ticket))
{
Name = "线程三"
};
Thread tD = new(new ThreadStart(p.Ticket))
{
Name = "线程四"
};
tA.Start();
tB.Start();
tC.Start();
tD.Start();
Console.ReadLine();
}
/// <summary>
/// 使用Monitor实现线程同步
/// </summary>
void Ticket()
{
while (true) //设置无限循环
{
Monitor.Enter(this); //锁定当前线程
if (num > 0) //判断当前票数是否大于0
{
Thread.Sleep(100); //使当前线程休眠100毫秒
Console.WriteLine(Thread.CurrentThread.Name + "----票数" + num--);//票数减1
}
Monitor.Exit(this); //释放当前线程
}
}
}
}
线程一----票数10
线程二----票数9
线程三----票数8
线程四----票数7
线程一----票数6
线程二----票数5
线程三----票数4
线程四----票数3
线程一----票数2
线程二----票数1
???????3.使用Mutex类实现线程同步
- 当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。
- Mutex类是同步基元,它只向一个线程授予对共享资源的独占访问权。
- 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
- Mutex类与监视器类似,它防止多个线程在某一时间同时执行某个代码块,然而与监视器不同的是,Mutex类可以用来使跨进程的线程同步。
- 可以使用WaitHandle.WaitOne方法请求互斥体的所属权,拥有互斥体的线程可以在对WaitOne()方法的重复调用中请求相同的互斥体而不会阻止其执行,但线程必须调用同样多次数的ReleaseMutex()方法以释放互斥体的所属权。
- Mutex类强制线程标识,因此互斥体只能由获得它的线程释放。
- 当用于进程间同步时,Mutex称为“命名Mutex”,因为它将用于另一个应用程序,因此它不能通过全局变量或静态变量共享。必须给它指定一个名称,才能使两个应用程序访问同一个Mutex对象。
????????Mutex类的常用方法及说明
方法 | 说 ???明 |
Close | ????在派生类中被重写时,释放由当前WaitHandle持有的所有资源 |
OpenExisting | ????打开现有的已命名互斥体 |
ReleaseMutex | ????释放Mutex一次 |
SignalAndWait | ????原子操作的形式,向一个WaitHandle发出信号并等待另一个 |
WaitAll | ????等待指定数组中的所有元素都收到信号 |
WaitAny | ????等待指定数组中的任一元素收到信号 |
WaitOn | ????当在派生类中重写时,阻止当前线程,直到当前的WaitHandle收到信号 |
????????使用Mutex类实现线程同步很简单,首先实例化一个Mutex类对象,它的构造函数中比较常用的有public?Mutex(bool initallyOwned)。
????????其中,参数initallyOwned指定了创建该对象的线程是否希望立即获得其所有权,当在一个资源得到保护的类中创建Mutex类对象时,常将该参数设置为false。然后在需要单线程访问的地方调用其等待方法,等待方法请求Mutex对象的所有权。
????????这时,如果该所有权被另一个线程所拥有,则阻塞请求线程,并将其放入等待队列中,请求线程将保持阻塞,直到Mutex对象收到了其所有者线程发出将其释放的信号为止。所有者线程在终止时释放Mutex对象,或者调用ReleaseMutex()方法来释放Mutex对象。
// 自定义了一个LockThread()方法,该方法中首先使用Mutex类对象的WaitOne()方法阻止当前线程。
// 然后再调用Mutex类对象的ReleaseMutex()方法释放Mutex对象,即释放当前的线程。
// 最后在Main()方法中通过Program的类对象调用LockThread()自定义方法
namespace _08
{
class Program
{
int num = 10;//设置当前总票数
/// <summary>
/// 创建对象,以便调用对象方法
/// 分别实例化4个线程,并设置名称
/// 分别启动线程
/// </summary>
static void Main(string[] args)
{
Program p = new();
Thread tA = new(new ThreadStart(p.Ticket))
{
Name = "线程一"
};
Thread tB = new(new ThreadStart(p.Ticket))
{
Name = "线程二"
};
Thread tC = new(new ThreadStart(p.Ticket))
{
Name = "线程三"
};
Thread tD = new(new ThreadStart(p.Ticket))
{
Name = "线程四"
};
tA.Start();
tB.Start();
tC.Start();
tD.Start();
Console.ReadLine();
}
/// <summary>
/// 使用Mutex实现线程同步
/// </summary>
void Ticket()
{
while (true)//设置无限循环
{
Mutex myMutex = new(false); //创建Mutex类对象
myMutex.WaitOne(); //阻塞当前线程
if (num > 0) //判断当前票数是否大于0
{
Thread.Sleep(200); //使当前线程休眠100毫秒
Console.WriteLine(Thread.CurrentThread.Name + "----票数" + num--);//票数减1
}
myMutex.ReleaseMutex(); //释放Mutex对象
}
}
}
}
线程一----票数10
线程四----票数9
线程二----票数8
线程三----票数10
线程四----票数6
线程三----票数7
线程二----票数5
线程一----票数4
线程四----票数3
线程二----票数1
线程一----票数2
线程三----票数0
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!