C# 多线程技术入门 Thread (一)
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
一、使用C#创建线程
1.1加入using指令
using System.Threading; //引入线程执行库文件
1.2 书写线程执行的方法
public static void add() { //创建线程执行的方法 Console.WriteLine("输出1-99的数字:"); for(int i = 1; i < 11;i++) { WriteLine(i); } }
1.3 创建Thread进程(一般在Main方法中)
Thread thread = new Thread(add); //声明线程对象,并指定执行方法 thread.Start(); //开启线程
1.4 暂停进程
Thread.Sleep(2000); //2000 毫秒
或者
Thread.Sleep(TimeSpan.FromSeconds(2)); //TimeSpan.FromSeconds(i) 返回毫秒值,i为秒
1.5 线程等待
Thread thread = new Thread(add); thread.Start(); thread.Join(); //当前线程执行完毕后,其他线程再执行
1.6 终止进程(不建议使用)
thread.Abort(); //会引起BUG,不建议采用
1.7 检测线程状态
CurrentThread.Threadstate.ToString(); //状态值,一般为 Unstarted、Running、 t.Threadstate.ToString(); //WaitSleepJoin、AbortRequested、Stopped
请注意始终可以通过Thread.CurrentThread静态属性获得当前Thread对象。
线程状态从Unstarted > Running > AbortRequested(WaitSleepJoin) > Stopped,其中AbortRequested(WaitSleepJoin)是根据线程暂停和等待状态所决定的。
1.7 线程优先级
当主程序启动时定义了两个不同的线程.,第一个线程优先级为ThreadPriority.Highest,即具有最高优先级。第二个线程优先级为ThreadPriority.Lowest,即具有最低优先级。我们先打印出主线程的优先级值,然后在所有可用的CPU核心上启动这两个线程。
using System; using System.Threading; using static System.Console; using static System,Threading.Thread; using static System.Diagnostics.Process; static void RunThreads() { var sample = new Threadsample(); var threadOne = new Thread(sample.CountNumbers); threadOne.Name = "ThreadOne"; var threadTwo = new Thread(sample.CountNumbers); threadTwo.Name = "ThreadTwo"; threadOne.Priority = ThreadPriori ty.Highest; threadTwo.Priority =. Threadpriority.Lowest; threadOne.Start(); threadTwo.Start (); Sleep(Timespan.FromSecpnds(2)); sample.Stop(); } class Threadsample { private bool _isStopped = false; public void Stop() { _is Stopped = true; } public void CountNumbers() { Long counter = 0; while (!_isStopped) { counter++; } WriteLine($"{CurrentThread.Name} with " + $"{CurrentThread.Priority,11} priority" + $"has a count = (counter, 13:NO}"); } } static void Main(string[] args) { WriteLine($"Current thread priority :{ CurrentThread.Priority}"); WriteLine("Running on all cores available"); RunThreads(); Sleep(TimeSpan.FromSeconds(2)); WriteLine("Running on a single core"); GetCurrentProcess 0.ProcessorAffinity = new IntPtr(1); RunThreads(); }
请注意这是操作系统使用线程优先级的一个演示。通常你无需使用这种行为编写程序。
1.8 前台线程与后台线程
示例代码:
class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations = iterations; } public void CounCNumbers() { for (int i = 0; i < _iterations; i++) { Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine($"{CurrentThread.Name} prints{i}"); } } static void Main(string[] args) { var sampleForegrourid = new ThreadSample(10); var sampleBackground = new ThreadSample(20); var threadOne = new Thread(sampleForegrourid.CounCNumbers); threadOne.Name = "ForegroundThread"; var threadTwo = new Thread(sampleBackground.CounCNumbers); threadTwo.Name ="BackgroundThread"; threadTwo.IsBackground = true; threadOne.Start(); threadTwo.Start(); } }
当主程序启动时定义了两个不同的线程。默认情况下’显式创建的线程是前台线程。通过手动的设置threadTwo对象的IsBackground属性为true来创建一个后台线程口通过配置来实现第一个线程会比第二个线程先完成。然后运行程序。
第一个线程完成后,程序结束并且后台线程被终结。这是前台线程与后台线程的主要区别:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
一个重要注意事项是如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。
1.9 向线程传递参数
示例代码:
using System; using System.Threading; using static System.Console; using static System.Threading.Thread; static void Main(string[] args) { //创建线程执行方法的对象,并传递参数3; var sample = new ThreadSample(3); //创建线程,并传递参数sample.CountNumbers,该处传递的是对象中的方法; var threadOne = new Thread(sample.CountNumbers); threadOne.Name = "ThreadOne"; threadOne.Start(); threadOne.Join(); WriteLine("--------------------------"); //创建线程,并传递参数Count方法,该处方法间接调用CountNumbers方法,在start方法中添加参数5; var threadTwo = new Thread(Count); threadTwo.Name = "ThreadTwo"; threadTwo.Start(5); threadTwo.Join(); WriteLine("--------------------------"); //创建线程,并传递参数,指定方法与值 () => CountNumbers(6); var threadThree = new Thread(() => CountNumbers(6)); threadThree.Name = "ThreadThree"; threadThree.Start(); threadThree.Join(); WriteLine("--------------------------"); //也可直接创建线程并传递参数,请注意,下列代码的执行效果,i=10 并未起效; int i = 10; var threadFour = new Thread(() => PrintNumber(i)); i = 20; var threadFive = new Thread(() => PrintNumber(i)); threadFour.Start(); threadFive.Start(); } //COUNT方法 static void Count(object iterations) { CountNumbers((int)iterations); } //CountNumbers方法 static void CountNumbers(int iterations) { for (int i = 1; i <= iterations; i++) { Sleep(TimeSpan.FromSeconds(0.5)); WriteLine($"{CurrentThread.Name} prints {i}"); } } //线程执行方法对象ThreadSample class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations = iterations; } public void CountNumbers() { for (int i = 1; i <= _iterations; i++) { Sleep(TimeSpan.FromSeconds(0.5)); WriteLine($"{CurrentThread.Name} prints {i}"); } } }
1.10 使用C#中的lock关键字
当主程序启动时,创建了一个Counter类的对象。该类定义了一个可以递增和递减的简单的计数器。然后我们启动了三个线程。这三个线程共享同一个counter实例,在一个周期中进行一次递增和一次递减。这将导致不确定的结果。如果运行程序多次,则会打印出多个不同的计数器值。结果可能是0,但大多数情况下则不是0。
当多个线程同时访问counter对象时,第一个线程得到的counter值10并增加为1".然后第二个线程得到的值是11并增加为12。第一个线程得到counter值12,但是递减操作发生前,第二个线程得到的counter值也是12。然后第一个线程将12递减为11并保存回counter中,同时第二个线程进行了同样的操作。结果我们进行了两次递增操作但是只有一次递减操作,这显然不对。这种情形被称为竞争条件(race condition) o竞争条件是多线程环境中非常常见的导致错误的原因。
using System; using System.Threading; using static System.Console; namespace Chapter1.Recipe9 { class Program { static void Main(string[] args) { WriteLine("Incorrect counter"); var c = new Counter(); var t1 = new Thread(() => TestCounter(c)); var t2 = new Thread(() => TestCounter(c)); var t3 = new Thread(() => TestCounter(c)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); WriteLine($"Total count: {c.Count}"); WriteLine("--------------------------"); WriteLine("Correct counter"); var c1 = new CounterWithLock(); t1 = new Thread(() => TestCounter(c1)); t2 = new Thread(() => TestCounter(c1)); t3 = new Thread(() => TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); WriteLine($"Total count: {c1.Count}"); Console.Read(); } static void TestCounter(CounterBase c) { for (int i = 0; i < 100000; i++) { c.Increment(); c.Decrement(); } } class Counter : CounterBase { public int Count { get; private set; } public override void Increment() { Count++; } public override void Decrement() { Count--; } } class CounterWithLock : CounterBase { private readonly object _syncRoot = new Object(); public int Count { get; private set; } public override void Increment() { lock (_syncRoot) { Count++; } } public override void Decrement() { lock (_syncRoot) { Count--; } } } abstract class CounterBase { public abstract void Increment(); public abstract void Decrement(); } } }
为了确保不会发生以上情形,必须保证当有线程操作counter对象时,所有其他线程必须等待直到当前线程完成操作。我们可以使用lock关键字来实现这种行为。如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。