【多线程】初识多线程
1. 為什么要學習多線程?
首先相信各位小伙伴在學習 JavaSE 的時候,肯定寫過一些小游戲吧,比如猜數字,關機小程序...但是如果現在要在猜數字小游戲上面加上一個功能,設定20秒沒猜中,就判定游戲失敗,那么請問,以沒學過多線程的角度來看,你要如何增加這樣的一個計時功能呢?即一遍玩游戲,一邊計時。
上述只是方便各位理解多線程,本質多線程的意義就是能充分利用 CPU 的多核資源,解決并發編程的問題!
現在市面上買到筆記本所配的CPU,比如是八核十六線程,這里是指該 CPU 有八個物理核心,而每個物理核心上又虛擬了兩個邏輯核心(超線程技術),對應的就是十六個線程,所以通過設備管理器會顯示出十六個核心,一個線程對應一個核心(邏輯核心)。
所以上述說到的十六個線程指CPU最多可以同時有十六個線程處理任務!
2. 進程的認識
2.1 進程與線程的關系
相信大家都有打開過自己電腦的任務管理器,里面可以發現運行著各種軟件,我們認為一個跑起來的程序,就是一個進程,進程與線程是包含的關系,一個進程中可以有多個線程!
其實也有很多軟件是采用多進程實現并發編程的,比如谷歌瀏覽器,每打開一個頁面就打開了一個進程,為什么可以采取多進程實現并發編程,但是還要使用多線程呢?
這里給各位小伙伴舉個例子,相信大家就很容易理解了:
如果說,籃球哥火了,最近籃球的訂單量特別大,一個工廠的生產線都已經忙不過來了,于是我的貼心助手給了我兩套解決方案:
第一套方案:
這是第一種解決方案,就是讓我再租個地,在建一個工廠,搞一套機器設備。
顯然呀,這又要去租個新的地了,又要蓋工廠,還得有一套新的物流體系,哎呀,這真的是又費錢又費力氣呀,于是我搖搖頭,這不行,換方案!其實上述這樣的方案,就類似于多進程!
第二套方案:
這是第二種解決方案,再原有的工廠內部,再開辟一條生產流水線。
此時場地和物流體系,是不是就可以復用原來場子的呀,也不用再去租場地建廠子了,共用一個場子就好了!很明顯第二種解決方案,成本上比第一種小很多!
根據上述的例子,就可以把籃球哥的工廠想象成進程,工廠里的生產流水線想象成線程!
總結:一個進程最少包含一個線程,也可以包含多個線程,同一個進程里的多個線程之間,共用了同一份資源(主要是 內存 和 文件描述符表) 比如:在線程1 new 的對象,線程2,線程3都可以使用,在線程1打打開的文件,線程2,線程3都可以使用。
接著回答上面的問題,能使用多進程編程,為什么還要用線程?
其實上述舉的例子也能清晰說明,創建一個進程開銷是很大的!建工廠,搞生產線,弄一套物流...而創建線程只需要在原有的進程里開辟,比如只用在原來工廠多搞一套生產線即可。
銷毀進程開銷也很大呀!假如籃球哥不火了,訂單變小了,只需要一個廠生產了,另一個廠就得銷毀掉,還得拆物流,拆生產線,拆工廠,而如果是一個工廠有兩套流水線,只需要拆其中一套流水線即可。
對于CPU來說,調度進程開銷也比較大(后續講解)
有了上述的情況線程就應運而生了,線程也叫做輕量級進程,這樣的出現,在解決并發編程問題的前提下,讓創建,銷毀,調度的速度更快!
2.2 進程間如何通信?
針對進程使用的內存空間,進行了 "隔離",即引入了虛擬地址空間,代碼里不在直接使用真實的物理地址了,而是使用虛擬的地址,由操作系統和專門的硬件負責進行虛擬地址到物理地址的轉換。主要目的是為了防止進程1內存越界,影響到線程2的運行,這里我們簡單了解,不深入討論。
雖然有了進程隔離,但還是會引入新的問題,有些時候,不同的進程之間,需要進行數據的交互,這就涉及到進程之間如何通信呢?
其實是在隔離的基礎上,開了個口子,搞一個多個進程都能訪問到的 "公共空間",基于這個空間來進行交互數據即可,我們后續主要講述:基于文件交互,基于網絡交互。現在簡單了解下即可。
3. 計算機如何描述進程的?
3.1 概述
進程是一個重要的軟件資源,是由操作系統內核負責管理的。
使用了結構體(C語言的結構體)來描述了進程的屬性,對于這個描述進程屬性的結構體有一個名字,叫做 PCB(進程控制塊),每個線程也對應一個 PCB!所以一個進程中可以有多個線程,也就是多個 PCB!
如何把這些PCB管理起來呢?用一個類似于雙向鏈表的結構,把多個 PCB 給串到一起。
創建一個進程,本質就是創建一個 PCB,給插入到鏈表中銷毀一個進程,本質就是把鏈表上的 PCB 節點給刪除掉
通過任務管理器看到的進程列表,本質就是遍歷這個 PCB 鏈表
注意:一個進程里可以有多個線程,每個線程對應一個PCB,但是這些 PCB 并不是完全獨立的,而是有共享的部分,比如一個進程里的多個 PCB 之間,pid是一樣的(表示這個進程唯一標識),內存指針和文件描述符表也是一樣的!
那么現在我們就要了解 PCB 包含了哪些描述進程的特征!
3.2 PCB 是如何描述進程的?
3.2.1 pid
pid 這個是進程里身份唯一的標識符,雖然一個進程里有多個線程,而這每個線程都對應一個 PCB 但是這些 PCB 的pid 都是一樣的!用來標識這些線程是屬于哪個進程的,也是這個進程的唯一標識符!
3.2.2 內存指針
內存指針,指向了該進程占用的內存是哪些,同理,雖然一個進程里包含多個線程,但是多個線程共享都是進程的資源,也就是利用了同一塊內存,所以一個進程中線程對應PCB里內存指針,也是一樣的!
3.3.3 文件描述符表
文件描述符表,表示打開的硬盤上的文件等其他資源,比如打開一個 .txt 文件,也就會對應在文件描述符表上新增一個表項(類似于數組的結構),后續學到的網絡編程,也會被當成文件,也會在文件描述符表上申請表項,跟詳細內容在文件 IO 章節講解。
3.3.4 進程調度相關屬性(下述屬性每個線程都有自己的)
線程的狀態:
就緒狀態:隨叫隨到,線程隨時準備去 CPU 上執行
運行狀態:正在 CPU 上執行的,很多操作系統,不會明確區分就緒和運行狀態
阻塞狀態:短時間內無法到CPU上運行了(后續文章會詳細講解)
優先級:
先讓哪個線程在 CPU 上運行,后讓哪個線程在 CPU 上運行,這也是有一定的優先級的,操作系統進線程調度的時候并不是很公平的。
上下文:
操作系統在進行線程謝歡的時候,就需要把進程執行的"中間狀態"記錄下來,保存好,下次這個線程再到 CPU 上去運行的時候,就可以恢復上次的狀態,繼續往下執行,本質上可以理解為 "存檔和讀檔",就是 CPU 中各個寄存器的值,保存上下文,就是把這些 CPU 寄存器的值記錄保存到內存中(PCB),回復上下文,就是把內存中這些寄存器的值恢復回去。
記賬信息:
操作系統,統計每個線程在 CPU 上占用的時間和執行的指令數目,根據這個來決定下一階段如何調度。
3.3.5 總結
PCB 這里包含的屬性是非常多的,不止我們上面所說的 pid,內存指針,文件描述符表,調度相關屬性,還有其他內容,上述內容只是一些核心的屬性,對于我們程序猿來說,了解以上屬性已經是差不多了。
但是從調度相關屬性開始,就有些小伙伴看不明白了,什么是調度?
4. 線程的調度
如何理解調度?從字面理解,你的領導將把你從北京公司調度到上海分公司去,而在 CPU 中,調度就是把一個線程拿到 CPU 核心上執行任務!
進程是 CPU 資源分配的最小單位,而線程是 CPU 調度的最小單位!
要了解線程的調度,首先要知道兩個概念,并行和并發:
并行:簡單來說,兩個線程在 CPU 的兩個核心上運行
并發:簡單來說,兩個線程在一個 CPU 核心上運行,對兩個線程進行快速切換
并行好理解,并發可能就不太好理解了!
舉個例子,張三喜歡打游戲,突然女朋友給打電話了,此時張三的腦子里就有兩個信號,第一個處理游戲,第二個處理女朋友的電話,可是既不能放下游戲,也不能掛掉女朋友電話呀,于是張三就一邊打游戲,一邊接電話,只要張三腦子在游戲電話這兩個任務中切換的夠快,不錯過游戲的內容,也不錯過女朋友的電話內容,此時就能做到無縫銜接!
放在 CPU 中,先運行一下QQ音樂,再運行一下再運行下畫圖板,再運行一下QQ音樂,再運行畫圖板,只要切換的速度足夠快(3.2GHz,每秒約運行32億條指令),從我們的角度看是感知不到了,看起來好像是這幾個線程再同時運行。
有了上面的理解,因此往往把并行和并發統稱為并發,除非顯式聲明,否則后續我們說到的并發都是 并行+并發
問題來了,CPU 的核心數有限,如果一個核心上并發運行多個線程,雖然我們知道是進行快速的切換,但是他們是隨機切換的!也就是 CPU 是隨機調度的,線程是搶占式執行的!這一點后續內容會用代碼給大家驗證!此處大家了解即可。
5. 線程的缺點
前面我們談到,一個進程中可以有多個線程,就如同我們上述舉的工廠的例子有兩條生產線,現在假設要生產 100 個籃球,一條生產線生產一個籃球需要花 2 個小時,那么 100 個籃球就需要花 200 個小時,如果是兩條生產線,大概只需要花 100 個小時左右,那是不是生產線越多就越好呢?工廠能不能放下那么多生產線呢?
到這里問題來了,進程中的線程是不是也是越多越好呢?這里通過一個形象的例子來演示:
滑稽老鐵今天過生日,喊了自己的一個好哥們一起吃蛋糕:
注意:上述情況房子就像進程,里面有多個線程,共享這個蛋糕的資源,兩個人吃蛋糕可能綽綽有余,速度也不快不慢,但是我們往后看:
這時候另一個滑稽老鐵說,就咱們倆個人啊,一點氣氛都沒有,蛋糕那么大,吃都吃不完!能不能多喊幾個人來啊?這樣才不會浪費蛋糕呀!而且要是能把俺暗戀的對象也喊來該多好!
于是滑稽老鐵說,你想要氣氛對吧,那俺就把咱的好兄弟都喊過來!
此時滑稽越多,是不是吃蛋糕的速度就越快呢?并不是,桌子旁邊的位置是有限的,就好比 CPU 的核心數也是有限的,如果滑稽老鐵太多了(線程),就會導致正在吃蛋糕的老鐵沒辦法安心吃,一般一張桌子只能坐 12 個人,難不成一個人吃一口就換另一個人上來吃嗎?這樣的開銷反而浪費在了 "換人吃蛋糕" 這個時間上,就好比如開銷浪費在線程調度上了。
此時還有一個問題,一個滑稽老鐵吃了一口下來了,空的這個座位有兩個滑稽都想坐上去,可能就會打起來,誰也讓著誰,會引發線程安全問題,也就是線程不安全!
更有一個特殊的情況,兩個滑稽老鐵爭蛋糕上僅一份的巧克力,滑稽1把巧克力給搶走了!滑稽2搶不到巧克力就生氣!拿起蛋糕往滑稽1臉上拍。
也就是如果一個線程拋異常處理的不好,很有可能把整個進程給帶走了,其他老鐵都吃不成蛋糕了,其他線程也就掛了!
總結:線程模型,天然就是資源共享的,多個線程爭搶同一個資源,容易有線程安全問題。
進程模型,天然是資源隔離的,很少有線程安全問題,進程之間通信,如果多個進程訪問公共資源,才可能會出現線程安全問題。
本期只是簡單帶大家認識下多線程和進程,更多的是理論概念,后續博主則會根據代碼實例,來進一步帶大家走進多線程編程!
下期預告【多線程】Thread類
總結
以上是生活随笔為你收集整理的【多线程】初识多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓机更新系统会卡吗_都说安卓手机用一两
- 下一篇: 一定是最便宜的5G套餐,北京用户福利畅享