欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

大家應(yīng)該掌握的多線(xiàn)程編程

 更新時(shí)間:2018年01月06日 14:46:16   作者:Helius-黑牛  
這篇文章主要介紹了大家應(yīng)該掌握的多線(xiàn)程編程,具有一定借鑒價(jià)值,需要的朋友可以參考下

毫無(wú)疑問(wèn),多線(xiàn)程在各種編程語(yǔ)言中都占有比較重要的一個(gè)席位。不管你是初學(xué)者,還是資深的老司機(jī),多線(xiàn)程是在學(xué)習(xí),面試和工作中都要經(jīng)常被提及的一個(gè)話(huà)題,下面我們就來(lái)看一看具體的相關(guān)內(nèi)容。

1、多線(xiàn)程編程必備知識(shí)

1.1 進(jìn)程與線(xiàn)程的概念

當(dāng)我們打開(kāi)一個(gè)應(yīng)用程序后,操作系統(tǒng)就會(huì)為該應(yīng)用程序分配一個(gè)進(jìn)程ID,例如打開(kāi)QQ,你將在任務(wù)管理器的進(jìn)程選項(xiàng)卡看到QQ.exe進(jìn)程,如下圖:

進(jìn)程可以理解為一塊包含了某些資源的內(nèi)存區(qū)域,操作系統(tǒng)通過(guò)進(jìn)程這一方式把它的工作劃分為不同的單元。一個(gè)應(yīng)用程序可以對(duì)應(yīng)于多個(gè)進(jìn)程。

線(xiàn)程是進(jìn)程中的獨(dú)立執(zhí)行單元,對(duì)于操作系統(tǒng)而言,它通過(guò)調(diào)度線(xiàn)程來(lái)使應(yīng)用程序工作,一個(gè)進(jìn)程中至少包含一個(gè)線(xiàn)程,我們把該線(xiàn)程成為主線(xiàn)程。線(xiàn)程與進(jìn)程之間的關(guān)系可以理解為:線(xiàn)程是進(jìn)程的執(zhí)行單元,操作系統(tǒng)通過(guò)調(diào)度線(xiàn)程來(lái)使應(yīng)用程序工作;而進(jìn)程則是線(xiàn)程的容器,它由操作系統(tǒng)創(chuàng)建,又在具體的執(zhí)行過(guò)程中創(chuàng)建了線(xiàn)程。

1.2線(xiàn)程的調(diào)度

在操作系統(tǒng)的書(shū)中貌似有提過(guò),“Windows是搶占式多線(xiàn)程操作系統(tǒng)”。之所以這么說(shuō)它是搶占式的,是因?yàn)榫€(xiàn)程可以在任意時(shí)間里被搶占,來(lái)調(diào)度另一個(gè)線(xiàn)程。操作系統(tǒng)為每個(gè)線(xiàn)程分配了0-31中的某一級(jí)優(yōu)先級(jí),而且會(huì)把優(yōu)先級(jí)高的線(xiàn)程優(yōu)先分配給CPU執(zhí)行。

Windows支持7個(gè)相對(duì)線(xiàn)程優(yōu)先級(jí):Idle、Lowest、BelowNormal、Normal、AboveNormal、Highest和Time-Critical。其中,Normal是默認(rèn)的線(xiàn)程優(yōu)先級(jí)。程序可以通過(guò)設(shè)置Thread的Priority屬性來(lái)改變線(xiàn)程的優(yōu)先級(jí),該屬性的類(lèi)型為T(mén)hreadPriority枚舉類(lèi)型,其成員包括Lowest、BelowNormal、Normal、AboveNormal和Highest。CLR為自己保留了Idle和Time-Critical兩個(gè)優(yōu)先級(jí)。

1.3線(xiàn)程也分前后臺(tái)

線(xiàn)程有前臺(tái)線(xiàn)程和后臺(tái)線(xiàn)程之分。在一個(gè)進(jìn)程中,當(dāng)所有前臺(tái)線(xiàn)程停止運(yùn)行后,CLR會(huì)強(qiáng)制結(jié)束所有仍在運(yùn)行的后臺(tái)線(xiàn)程,這些后臺(tái)線(xiàn)程被直接終止,卻不會(huì)拋出任何異常。主線(xiàn)程將一直是前臺(tái)線(xiàn)程。我們可以使用Tread類(lèi)來(lái)創(chuàng)建前臺(tái)線(xiàn)程。

using System;
using System.Threading;

namespace 多線(xiàn)程1
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var backThread = new Thread(Worker);
      backThread.IsBackground = true;
      backThread.Start();
      Console.WriteLine("從主線(xiàn)程退出");
      Console.ReadKey();
    }

    private static void Worker()
    {
      Thread.Sleep(1000);
      Console.WriteLine("從后臺(tái)線(xiàn)程退出");
    }
  }
}

以上代碼先通過(guò)Thread類(lèi)創(chuàng)建了一個(gè)線(xiàn)程對(duì)象,然后通過(guò)設(shè)置IsBackground屬性來(lái)指明該線(xiàn)程為后臺(tái)線(xiàn)程。如果不設(shè)置這個(gè)屬性,則默認(rèn)為前臺(tái)線(xiàn)程。接著調(diào)用了Start的方法,此時(shí)后臺(tái)線(xiàn)程會(huì)執(zhí)行Worker函數(shù)的代碼。所以在這個(gè)程序中有兩個(gè)線(xiàn)程,一個(gè)是運(yùn)行Main函數(shù)的主線(xiàn)程,一個(gè)是運(yùn)行Worker線(xiàn)程的后臺(tái)線(xiàn)程。由于前臺(tái)線(xiàn)程執(zhí)行完畢后CLR會(huì)無(wú)條件地終止后臺(tái)線(xiàn)程的運(yùn)行,所以在前面的代碼中,若啟動(dòng)了后臺(tái)線(xiàn)程,則主線(xiàn)程將會(huì)繼續(xù)運(yùn)行。主線(xiàn)程執(zhí)行完后,CLR發(fā)現(xiàn)主線(xiàn)程結(jié)束,會(huì)終止后臺(tái)線(xiàn)程,然后使整個(gè)應(yīng)用程序結(jié)束運(yùn)行,所以Worker函數(shù)中的Console語(yǔ)句將不會(huì)執(zhí)行。所以上面代碼的結(jié)果是不會(huì)運(yùn)行Worker函數(shù)中的Console語(yǔ)句的。

可以使用Join函數(shù)的方法,確保主線(xiàn)程會(huì)在后臺(tái)線(xiàn)程執(zhí)行結(jié)束后才開(kāi)始運(yùn)行。

using System;
using System.Threading;

namespace 多線(xiàn)程1
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var backThread = new Thread(Worker);
      backThread.IsBackground = true;
      backThread.Start();
      backThread.Join();
      Console.WriteLine("從主線(xiàn)程退出");
      Console.ReadKey();
    }

    private static void Worker()
    {
      Thread.Sleep(1000);
      Console.WriteLine("從后臺(tái)線(xiàn)程退出");
    }
  }
}

以上代碼調(diào)用Join函數(shù)來(lái)確保主線(xiàn)程會(huì)在后臺(tái)線(xiàn)程結(jié)束后再運(yùn)行。

如果你線(xiàn)程執(zhí)行的方法需要參數(shù),則就需要使用new Thread的重載構(gòu)造函數(shù)Thread(ParameterizedThreadStart).

using System;
using System.Threading;

namespace 多線(xiàn)程1
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var backThread = new Thread(new ParameterizedThreadStart(Worker));
      backThread.IsBackground = true;
      backThread.Start("Helius");
      backThread.Join();
      Console.WriteLine("從主線(xiàn)程退出");
      Console.ReadKey();
    }

    private static void Worker(object data)
    {
      Thread.Sleep(1000);
      Console.WriteLine($"傳入的參數(shù)為{data.ToString()}");
    }
  }
}

執(zhí)行結(jié)果為:

2、線(xiàn)程的容器——線(xiàn)程池

前面我們都是通過(guò)Thead類(lèi)來(lái)手動(dòng)創(chuàng)建線(xiàn)程的,然而線(xiàn)程的創(chuàng)建和銷(xiāo)毀會(huì)耗費(fèi)大量時(shí)間,這樣的手動(dòng)操作將造成性能損失。因此,為了避免因通過(guò)Thread手動(dòng)創(chuàng)建線(xiàn)程而造成的損失,.NET引入了線(xiàn)程池機(jī)制。

2.1 線(xiàn)程池

線(xiàn)程池是指用來(lái)存放應(yīng)用程序中要使用的線(xiàn)程集合,可以將它理解為一個(gè)存放線(xiàn)程的地方,這種集中存放的方式有利于對(duì)線(xiàn)程進(jìn)行管理。

CLR初始化時(shí),線(xiàn)程池中是沒(méi)有線(xiàn)程的。在內(nèi)部,線(xiàn)程池維護(hù)了一個(gè)操作請(qǐng)求隊(duì)列,當(dāng)應(yīng)用程序想要執(zhí)行一個(gè)異步操作時(shí),需要調(diào)用QueueUserWorkItem方法來(lái)將對(duì)應(yīng)的任務(wù)添加到線(xiàn)程池的請(qǐng)求隊(duì)列中。線(xiàn)程池實(shí)現(xiàn)的代碼會(huì)從隊(duì)列中提取,并將其委派給線(xiàn)程池中的線(xiàn)程去執(zhí)行。如果線(xiàn)程池沒(méi)有空閑的線(xiàn)程,則線(xiàn)程池也會(huì)創(chuàng)建一個(gè)新線(xiàn)程去執(zhí)行提取的任務(wù)。而當(dāng)線(xiàn)程池線(xiàn)程完成某個(gè)任務(wù)時(shí),線(xiàn)程不會(huì)被銷(xiāo)毀,而是返回到線(xiàn)程池中,等待響應(yīng)另一個(gè)請(qǐng)求。由于線(xiàn)程不會(huì)被銷(xiāo)毀,所以也就避免了性能損失。記住,線(xiàn)程池里的線(xiàn)程都是后臺(tái)線(xiàn)程,默認(rèn)級(jí)別是Normal。

2.2 通過(guò)線(xiàn)程池來(lái)實(shí)現(xiàn)多線(xiàn)程

要使用線(xiàn)程池的線(xiàn)程,需要調(diào)用靜態(tài)方法ThreadPool.QueueUserWorkItem,以指定線(xiàn)程要調(diào)用的方法,該靜態(tài)方法有兩個(gè)重載版本:

public static bool QueueUserWorkItem(WaitCallback callBack);

public static bool QueueUserWorkItem(WaitCallback callback,Object state)

這兩個(gè)方法用于向線(xiàn)程池隊(duì)列添加一個(gè)工作先以及一個(gè)可選的狀態(tài)數(shù)據(jù)。然后,這兩個(gè)方法就會(huì)立即返回。下面通過(guò)實(shí)例來(lái)演示如何使用線(xiàn)程池來(lái)實(shí)現(xiàn)多線(xiàn)程編程。

using System;
using System.Threading;

namespace 多線(xiàn)程2
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine($"主線(xiàn)程ID={Thread.CurrentThread.ManagedThreadId}");
      ThreadPool.QueueUserWorkItem(CallBackWorkItem);
      ThreadPool.QueueUserWorkItem(CallBackWorkItem,"work");
      Thread.Sleep(3000);
      Console.WriteLine("主線(xiàn)程退出");
      Console.ReadKey();
    }

    private static void CallBackWorkItem(object state)
    {
      Console.WriteLine("線(xiàn)程池線(xiàn)程開(kāi)始執(zhí)行");
      if (state != null)
      {
        Console.WriteLine($"線(xiàn)程池線(xiàn)程ID={Thread.CurrentThread.ManagedThreadId},傳入的參數(shù)為{state.ToString()}");
      }
      else
      {
        Console.WriteLine($"線(xiàn)程池線(xiàn)程ID={Thread.CurrentThread.ManagedThreadId}");
      }
    }
  }
}

結(jié)果為:

2.3 協(xié)作式取消線(xiàn)程池線(xiàn)程

.NET Framework提供了取消操作的模式,這個(gè)模式是協(xié)作式的。為了取消一個(gè)操作,必須創(chuàng)建一個(gè)System.Threading.CancellationTokenSource對(duì)象。下面還是使用代碼來(lái)演示一下:

using System;
using System.Threading;

namespace 多線(xiàn)程3
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      Console.WriteLine("主線(xiàn)程運(yùn)行");
      var cts = new CancellationTokenSource();
      ThreadPool.QueueUserWorkItem(Callback, cts.Token);
      Console.WriteLine("按下回車(chē)鍵來(lái)取消操作");
      Console.Read();
      cts.Cancel();
      Console.ReadKey();
    }

    private static void Callback(object state)
    {
      var token = (CancellationToken) state;
      Console.WriteLine("開(kāi)始計(jì)數(shù)");
      Count(token, 1000);
    }

    private static void Count(CancellationToken token, int count)
    {
      for (var i = 0; i < count; i++)
      {
        if (token.IsCancellationRequested)
        {
          Console.WriteLine("計(jì)數(shù)取消");
          return;
        }
        Console.WriteLine($"計(jì)數(shù)為:{i}");
        Thread.Sleep(300);
      }
      Console.WriteLine("計(jì)數(shù)完成");
    }
  }
}

結(jié)果為:

3、線(xiàn)程同步

線(xiàn)程同步計(jì)數(shù)是指多線(xiàn)程程序中,為了保證后者線(xiàn)程,只有等待前者線(xiàn)程完成之后才能繼續(xù)執(zhí)行。這就好比生活中排隊(duì)買(mǎi)票,在前面的人沒(méi)買(mǎi)到票之前,后面的人必須等待。

3.1 多線(xiàn)程程序中存在的隱患

多線(xiàn)程可能同時(shí)去訪(fǎng)問(wèn)一個(gè)共享資源,這將損壞資源中所保存的數(shù)據(jù)。這種情況下,只能采用線(xiàn)程同步技術(shù)。

3.2 使用監(jiān)視器對(duì)象實(shí)現(xiàn)線(xiàn)程同步

監(jiān)視器對(duì)象(Monitor)能夠確保線(xiàn)程擁有對(duì)共享資源的互斥訪(fǎng)問(wèn)權(quán),C#通過(guò)lock關(guān)鍵字來(lái)提供簡(jiǎn)化的語(yǔ)法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace 線(xiàn)程同步
{
  class Program
  {
    private static int tickets = 100;
    static object globalObj=new object();
    static void Main(string[] args)
    {
      Thread thread1=new Thread(SaleTicketThread1);
      Thread thread2=new Thread(SaleTicketThread2);
      thread1.Start();
      thread2.Start();
      Console.ReadKey();
    }

    private static void SaleTicketThread2()
    {
      while (true)
      {
        try
        {
          Monitor.Enter(globalObj);
          Thread.Sleep(1);
          if (tickets > 0)
          {
            Console.WriteLine($"線(xiàn)程2出票:{tickets--}");
          }
          else
          {
            break;
          }
        }
        catch (Exception)
        {
          throw;
        }
        finally
        {
          Monitor.Exit(globalObj);
        }
      }
    }

    private static void SaleTicketThread1()
    {
      while (true)
      {
        try
        {
          Monitor.Enter(globalObj);
          Thread.Sleep(1);
          if (tickets > 0)
          {
            Console.WriteLine($"線(xiàn)程1出票:{tickets--}");
          }
          else
          {
            break;
          }
        }
        catch (Exception)
        {
          throw;
        }
        finally
        {
          Monitor.Exit(globalObj);
        }
      }
    }
  }
}

在以上代碼中,首先額外定義了一個(gè)靜態(tài)全局變量globalObj,并將其作為參數(shù)傳遞給Enter方法。使用了Monitor鎖定的對(duì)象需要為引用類(lèi)型,而不能為值類(lèi)型。因?yàn)樵趯⒅殿?lèi)型傳遞給Enter時(shí),它將被先裝箱為一個(gè)單獨(dú)的毒香,之后再傳遞給Enter方法;而在將變量傳遞給Exit方法時(shí),也會(huì)創(chuàng)建一個(gè)單獨(dú)的引用對(duì)象。此時(shí),傳遞給Enter方法的對(duì)象和傳遞給Exit方法的對(duì)象不同,Monitor將會(huì)引發(fā)SynchronizationLockException異常。

3.3線(xiàn)程同步技術(shù)存在的問(wèn)題

(1)使用比較繁瑣。要用額外的代碼把多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)的數(shù)據(jù)包圍起來(lái),還并不能遺漏。

(2)使用線(xiàn)程同步會(huì)影響程序性能。因?yàn)楂@取和釋放同步鎖是需要時(shí)間的;并且決定那個(gè)線(xiàn)程先獲得鎖的時(shí)候,CPU也要進(jìn)行協(xié)調(diào)。這些額外的工作都會(huì)對(duì)性能造成影響。

(3)線(xiàn)程同步每次只允許一個(gè)線(xiàn)程訪(fǎng)問(wèn)資源,這會(huì)導(dǎo)致線(xiàn)程堵塞。繼而系統(tǒng)會(huì)創(chuàng)建更多的線(xiàn)程,CPU也就要負(fù)擔(dān)更繁重的調(diào)度工作。這個(gè)過(guò)程會(huì)對(duì)性能造成影響。

下面就由代碼來(lái)解釋一下性能的差距:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace 線(xiàn)程同步2
{
  class Program
  {
    static void Main(string[] args)
    {
      int x = 0;
      const int iterationNumber = 5000000;
      Stopwatch stopwatch=Stopwatch.StartNew();
      for (int i = 0; i < iterationNumber; i++)
      {
        x++;
      }
      Console.WriteLine($"不使用鎖的情況下花費(fèi)的時(shí)間:{stopwatch.ElapsedMilliseconds}ms");
      stopwatch.Restart();
      for (int i = 0; i < iterationNumber; i++)
      {
        Interlocked.Increment(ref x);
      }
      Console.WriteLine($"使用鎖的情況下花費(fèi)的時(shí)間:{stopwatch.ElapsedMilliseconds}ms");
      Console.ReadKey();
    }
  }
}

執(zhí)行結(jié)果:

總結(jié)

以上就是本文關(guān)于大家應(yīng)該掌握的多線(xiàn)程編程的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站其他相關(guān)專(zhuān)題,如有不足之處,歡迎留言指出。感謝朋友們對(duì)本站的支持!

相關(guān)文章

最新評(píng)論