Java 併發編程解析 | 關於線程機制的那些事,你究竟瞭解多少?

来源:https://www.cnblogs.com/mazhilin/archive/2022/07/30/16534198.html
-Advertisement-
Play Games

蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 眾所周知,在電腦操作系統中,進程(Process)是一個很關鍵的概念,最本質的理解就是操作系統執行的一個應用程式(Application Program)。與每個進程相關的是地址空間(Address ...


蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》

寫在開頭

眾所周知,在電腦操作系統中,進程(Process)是一個很關鍵的概念,最本質的理解就是操作系統執行的一個應用程式(Application Program)。與每個進程相關的是地址空間(Address Space)。其中,描述的是從某個最小值的存儲位置(通常是0)到最大值的存儲位置的列表。在這個地址空間中,進程可以進行讀寫操作。地址空間中可以存放可執行程式,以及程式需要的數據和棧針。與每個進程相關的資源集合。通常包括寄存器(Registers),打開的文件清單,突發的系統報警,有關的進程清單和其他執行程式的信息。其中,寄存器主要包括程式計數器(Program Counter)和堆棧指針(Stack Pointer)。從一定程度上,我們可以把進程當作容納運行一個程式所有信息的一個容器(Container)。

操作系統中可以使用進程來描述一個程式的執行過程,進程擁有該程式的所有數據(包括一些I/O分配情況、記憶體分配情況等),也就是該程式的一個載體,所以進程有一個特點就是資源分配的單位,這一點十分重要。進程還有一個特點就是調度執行,交替執行以提高資源利用率。

操作系統管理進程(創建、切換進程、分配與回收等)開銷是很大的,比如進程創建時還需要創建PCB,分配記憶體獨立的內空間,建立映射表,創建資源,進程切換時還需要切換資源,如切換對應的記憶體映射表,進程退出時還需要釋放資源。

由此不難得出一個結論,每一個進程都有一個地址空間(Address Space)和一個控制線程(Control Thead)。但是,操作系統有了進程為何要出現線程呢?主要是因為,雖然進程任然是資源分配的單位,但是調度執行卻交給了線程,因為線程是在進程的內部,線程間的切換不用切換資源,不用切換映射表,只需要簡單的在進程內部切換一下PC指針和保存一些寄存器即可,這也就更輕量了(避免不了不同進程間的線程切換)。

基本概述

線程(Thread)既保留了併發執行的優點,也避免了進程切換的代價。

假設現在有一個網路伺服器,此時沒有線程的概念,該伺服器程式用到多個進程,如用一個進程監聽客戶端的請求,當客戶端連接上後就分派出(複製出一個子進程)一個進程給該用戶(每個進程都有獨立的資源),用於監聽該用戶發送的數據並處理(即多進程程式),此時設想一下,這多個進程切來切去,每次切換的時候都需要切換資源,是不是很耗費資源。

此時,引入了線程之後,網路伺服器這個程式是一個進程,進程用於承載該程式的資源,首先用進程中的一個線程監聽請求,每次連接客戶端都分配一個線程給用戶(多線程程式),此時處理器只需要在這幾個線程中切換即可,線程的切換不需要切換資源(進程時資源級的切換,線程是指令級的切換),那麼多個線程只需要共用進程中的資源即可,其運行速度和執行效率也得到了提升。

由此可見,操作系統引入線程後,調度和分派是線上程上完成的,但是某些活動會影響進程中的所有線程,因此這些活動必須在進程級對他們進行管理。如掛起操作會掛起所有線程,因為所有線程共用進程的用戶地址空間。引入了線程最關鍵的體現在以下兩個方面:

  • 線程的創建、終止和調度更輕量
  • 線程間的通信不進過內核,不需要用戶態->內核態的轉換

但是,同時也增加了程式的開發難度,如果開發者對於線程機制的掌握和認識不夠準確,也會陷入技術困惑。

線程模型

所有線程共用進程的狀態和資源,所以線程都駐留在同一塊地址空間中,並可訪問相同的數據。

對於有線程和無線程的區別,其中主要是體現在用戶棧和內核棧兩個關鍵:

  • 用戶棧用於保存用戶進程的子程式間相互調用的參數、返回值以及局部變數等信息(保存普通方法的棧)
  • 內核棧是程式發生系統調用時內核態調用方法時的棧;用戶地址空間則是進程的程式和數據存放的空間,線程是沒有自己的用戶地址空間的

一般來說,用戶棧和內核棧已經線上程中獨有,也證明瞭線程成為了任務調度的基本單位,這些線程都共用進程所持有的資源,線程式控制制塊中存放了寄存器的值、優先順序、線程狀態等信息。

在操作系統層面,線程也有“生老病死”,專業的說法叫有生命周期。雖然不同的開發語言對於操作系統線程進行了不同的封裝,但是對於線程的生命周期這部分,基本上是相通的。每一個線程基本都有如下特征:

  1. 類似進程,線程也有執行狀態(生命周期),因為線程也是一個執行過程
  2. 線程的上下文,線程切換時也需要進行保護現場
  3. 執行棧,保存系統調用時的一些參數和中間結果
  4. 少量的,線程私有的局部變數的存儲空間,不再擁有大量的存儲空間
  5. 與進程內其他線程共用的記憶體和資源的訪問
  6. 線程式控制制塊TCB,存放上下文切換的信息,同PCB

可以看出,對於有生命周期的事物,要學好和掌握它,思路非常簡單,只要能搞懂生命周期中各個節點的狀態轉換機制即可。

線程分類

線程分為用戶級線程(User-Level Thread,ULT)和內核級線程(Kernel-Level Thread,KLT),內核級線程又叫做輕量級進程(Light-Weight Process,LWP)。

用戶級線程(User-Level Thread,ULT)

在純ULT軟體中,管理線程的所有工作都是應用程式完成,內核意識不到線程的存在,線程完全是由線程庫提供的,創建、銷毀、調度線程、線程間傳遞消息等,還包括保存上下文都是由它管控的,如果可以的話我們自己也可以實現自己的線程庫,只要合理的組織線程即可。

但是用戶級線程所有的活動都發生在用戶空間和一個進程中,系統感知不到用戶級線程的存在,所以系統依舊是以進程的方式調度。

當線程1發生系統調用等阻塞了,此時系統就會認為該進程阻塞了,操作系統會把CPU時間片分配給其他進程,在此期間,根據線程庫維護的數據結構來看,線程1任然處於運行狀態,但在處理器執行的角度,線程2是不處於運行狀態的,也分不到時間片。

這也導致了用戶級線程一旦阻塞,就會阻塞進程中的所有線程,使得其他線程也得不到運行。使用用戶級線程(ULT)如下特點:

優點:

  • 所有線程的管理都在一個進程的用戶空間中,線程的切換不需要內核模式特權,不需要系統調用,從而節省了用戶態到內核態轉換的開銷
  • 線程的調度更靈活,可以為每個不同的應用程式量身定製更合適的調度演算法,因為這些調度演算法都可以自己實現,不需要更改操作系統底層的調度程式
  • ULT可以在任何操作系統下運行,即便是不支持線程的操作系統也能實現,線程庫是供所有應用程式共用的一組應用級函數

缺點:

  • 在執行一個系統調用時不僅僅是阻塞當前線程,還會阻塞進程中的所有線程
  • ULT不能利用多處理技術,操作系統看不到線程,所以內核一次只能把一個進程分配給一個處理器,因此一個進程中的所有線程不能夠並行執行,只能夠併發執行,相當於一個進程內實現了多道程式設計

解決這兩個問題的方法有:

  • 把應用程式寫出多進程程式,但是該方法消除了線程的主要優點
  • 套管技術:把產生阻塞的系統調用轉化為一個非阻塞的系統調用

綜上所述,用戶級線程(User-Level Thread,ULT)適合計算密集型的,因為不需要IO操作 ,不會阻塞整個進程。

內核級線程(Kernel-Level Thread,KLT)

在KLT軟體中,管理線程的所有工作均由內核完成,應用級沒有線程管理代碼,只有一個到內核線程的API。

內核為進程級進程內的所有線程維護上下文信息,調度由內核基於線程完成。

該方法剋服了ULT的兩個缺點。首先,內核可以把一個進程中的線程分配個多個處理器中;其次,進程中的某個線程阻塞了,內核還可以調度同一個進程中的其他線程。

缺點是:在把控制權從一個線程傳送到另一個進程的線程時,需要切換到內核模式,開銷較大。

綜上所述,KLT併發性更好,適合I/O操作較多的程式。

混合線程(Hybrid-Approach Thread,HAT)

有些操作系統提供了ULT和KLT的混合體:線程創建完全在用戶空間中完成,線程的調度和同步也在應用程式中進行,一個應用程式中的多個用戶級線程會被映射到一些(小於等於用戶級線數)內核級線程上,進程和線程的比為 N:M,N<=M,ULT中比值為1:N,KLT為1:1

同一個應用程式中的多個線程可在多個處理器上並行的運行,某個引起阻塞的系統調用不會阻塞整個進程。

綜上所述,內核級線程(KLT)和用戶級線程(User-Level Thread,ULT)對比分析如下:

線程生命周期

一個線程的生命周期基本上可以這個“五態模型”來描述,主要分別是:初始狀態、可運行狀態、運行狀態、休眠狀態和終止狀態。其中:

  1. 初始狀態,指的是線程已經被創建,但是還不允許分配 CPU 執行。這個狀態屬於編程語言特有的,不過這裡所謂的被創建,僅僅是在編程語言層面被創建,而在操作系統層面,真正的線程還沒有創建。
  2. 可運行狀態,指的是線程可以分配 CPU 執行。在這種狀態下,真正的操作系統線程已經被成功創建了,所以可以分配 CPU 執行。
  3. 運行狀態:當有空閑的 CPU 時,操作系統會將其分配給一個處於可運行狀態的線程,被分配到 CPU 的線程的狀態就轉換成了運行狀態。
  4. 休眠狀態:運行狀態的線程如果調用一個阻塞的 API(例如以阻塞方式讀文件)或者等待某個事件(例如條件變數),那麼線程的狀態就會轉換到休眠狀態,同時釋放 CPU 使用權,休眠狀態的線程永遠沒有機會獲得 CPU 使用權。當等待的事件出現了,線程就會從休眠狀態轉換到可運行狀態。
  5. 終止狀態:線程執行完或者出現異常就會進入終止狀態,終止狀態的線程不會切換到其他任何狀態,進入終止狀態也就意味著線程的生命周期結束。

版權聲明:本文為博主原創文章,遵循相關版權協議,如若轉載或者分享請附上原文出處鏈接和鏈接來源。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 配置 Webpack Dev Server 可以解決本地開發前端應用時,手動執行 webpack 命令或 yarn build 命令,再去瀏覽器中訪問 dist/index.html 的麻煩耗時操作,可有效簡化流程。本文實戰演練配置 Webpack Dev Server 的方法與步驟。 ...
  • FrameDataTrans教程 博客園 乳鴿菌 20220729 核心原理是使用postMessage發送數據,window.addEventListener("message",fun)監聽。 插件地址 index.htm // <iframe id="fr" src="child.html"> ...
  • 記錄一個清除計時器的小bug,使用計時器的時候迴圈一直停不下來,把計時器賦值給了變數,仍舊無法停止。後來發現是清除器放置的位置有問題。 最初的位置: 這樣放置就導致if語句值運行了一次!我潛意識預設了整個getprogress方法為迴圈體,但其實只有那個計時器部分是迴圈體,導致我百思不得其解為什麼循 ...
  • 是不是見到google,facebook等大型專業網站的擁有不同的語言站群,可以不同語言間切換很給力 今天要介紹的就是如何識別不同國家語言,只需要簡單幾步,使你的web應用更有國際範。 安裝vue-i18n npm i vue-i18n --save 新建多語言json文件 在src目錄下新建 co ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 最近鼓搗了一下 Vue3 + Vite2,遇到了不少問題,整理了5個可以提高開發效率的小知識,讓你在 Vue3 的項目開發中更加絲滑、順暢。 一、setup name 增強 Vue3的setup語法糖是個好東西,但使用setup語法 ...
  • 最近寫二維碼的時候,突然想起之前項目遇到過的一個問題,網上也沒有這方面解答,想到大家今後可能也會遇到這類問題,在此記錄下來,希望對你們有所幫助,大佬們不喜勿噴,qrcode配合畫布canvas本地生成二維碼的時候第一次能夠正常顯示,最下方會貼出代碼 跨頁面後顯示異常 剛開始是有些頭疼的,因為控制台一 ...
  • 自定義事件 tips 推薦始終使用 kebab-case 的事件名。(v-on會將事件名自動轉換為小寫,避免匹配不到) changeData × change-data √ 自定義組件的v-model 用法: 父組件定義數據源(不需要定義修改數據的方法),在子組件標簽上通過v-model="data ...
  • 因為涉及到有些監控是沒有音頻的,所以使用flv.js插件,通過<web-view>標簽跳轉到h5頁面。 在真機上調試發現是沒有發現flv.js文件,出現跨域現象,但是我只是導入了js文件,不知道什麼情況; 然後,就只是移動端不支持flv.js。經過不懈努力,我找到了另外一個插件 DPlayer.js ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...