1. 程式人生 > >作業系統核心原理-4.執行緒原理(上):執行緒基礎與執行緒同步

作業系統核心原理-4.執行緒原理(上):執行緒基礎與執行緒同步

  我們都知道,程序是運轉中的程式,是為了在CPU上實現多道程式設計而發明的一個概念。但是程序在一個時間只能幹一件事情,如果想要同時幹兩件或者多件事情,例如同時看兩場電影,我們自然會想到傳說中的分身術,就像孫悟空那樣可以變出多個真身。雖然我們在現實中無法分身,但程序卻可以辦到,辦法就是執行緒。執行緒就是我們為了讓一個程序能夠同時幹多件事情而發明的“分身術”。

一、執行緒基礎

1.1 執行緒概念

  執行緒是程序的“分身”,是程序裡的一個執行上下文或執行序列。of course,一個程序可以同時擁有多個執行序列。這就像舞臺,舞臺上可以有多個演員同時出場,而這些演員和舞臺就構成了一齣戲。類比程序和執行緒,每個演員是一個執行緒,舞臺是地址空間,這樣同一個地址空間中的所有執行緒就構成了程序。

  線上程模式下,一個程序至少有一個執行緒,也可以有多個執行緒,如下圖所示:

  將程序分解為執行緒可以有效地利用多處理器和多核計算機。例如,當我們使用Microsoft Word時,實際上是打開了多個執行緒。這些執行緒一個負責顯示,一個負責接收輸入,一個定時進行存檔......這些執行緒一起運轉,讓我們感覺到輸入和顯示同時發生,而不用鍵入一些字元等待一會兒才顯示到螢幕上。在不經意間,Word還能定期自動儲存。

1.2 執行緒管理

  執行緒管理與程序管理類似,需要一定的基礎:維持執行緒的各種資訊,這些資訊包含了執行緒的各種關鍵資料。於是,就有了執行緒控制塊。

  由於執行緒間共享一個程序空間,因此,許多資源是共享的(這部分資源不需要存放線上程控制塊中)。但又因為執行緒是不同的執行序列,總會有些不能共享的資源。一般情況下,統一程序內的執行緒間共享和獨享資源的劃分如下表所示:

1.3 執行緒模型

  現代作業系統結合了使用者態和核心態的執行緒模型,其中使用者態的執行系統負責程序內部在非阻塞時的切換,而核心態的作業系統則負責阻塞執行緒的切換,即同時實現核心態和使用者態執行緒管理。其中,核心態執行緒數量極少,而使用者態執行緒數量較多。每個核心態執行緒可以服務一個或多個使用者態執行緒。換句話說,使用者態執行緒會被多路複用到核心態執行緒上。

1.4 多執行緒的關係

  推出執行緒模型的目的就是實現程序級併發,因為在一個程序中通常會出現多個執行緒。多個執行緒共享一個舞臺,時而互動,時而獨舞。但是,共享一個舞臺會帶來不必要的麻煩,這些麻煩歸結到下面兩個根本問題:

  (1)執行緒之間如何通訊?

  (2)執行緒之間如何同步?

  上述兩個問題在程序層面同樣存在,在前面的程序原理部分已經進行了介紹,從一個更高的層次上看,不同的程序也共享著一個巨大的空間,這個空間就是整個計算機。

二、執行緒同步

2.1 同步的原因和目的

  (1)原因

  執行緒之間的關係是合作關係,既然是合作,那麼久得有某種約定的規則,否則合作就會出問題。例如下圖中,一個程序的兩個執行緒因為操作不同步而造成執行緒1執行錯誤:

  出現上述問題原因在於兩點:一是執行緒之間共享的全域性變數;二是執行緒之間的相對執行順序是不確定的。針對第一點,如果所有資源都不共享,那就違背了程序和執行緒設計的初衷:資源共享、提高資源利用率。針對第二點,需要讓執行緒之間的相對執行順序在需要的時候可以確定

  (2)目的

  執行緒同步的目的就在於不管執行緒之間的執行如何穿插,其執行結果都是正確的。換句話說,就是要保證多執行緒執行下結果的確定性。與此同時,也要保持對執行緒執行的限制越少越少。

2.2 同步的方式

  (1)一些必要概念

  ① 兩個或多個執行緒爭相執行同一段程式碼或訪問同一資源的現象稱為競爭

  ② 可能造成競爭的共享程式碼段或資源就被稱為臨界區

  ③ 在任何時刻都能有一個執行緒在臨界區中的現象被稱為互斥。(一次只有一個人使用共享資源,其他人皆排除在外)

  (2)鎖

lock

  ① 關於鎖

  當兩個教師都想使用同一個教室來為學生補課,如何協調呢?進到教室後將門鎖上,另外一個教室就無法進來使用教室了。即教室是用鎖來保證互斥的,那麼在作業系統中,這種可以保證互斥的同步機制就被稱為鎖。

  例如,在.NET中可以直接使用lock語句來實現執行緒同步:

    private object locker = new object();
    public void Work()
    {
          lock (locker)
          {
              // 做一些需要執行緒同步的工作
          }
     }

  鎖有兩個基本操作:閉鎖和開鎖。很容易理解,閉鎖就是將鎖鎖上,其他人進不來;開鎖就是你做的事情做完了,將鎖開啟,別的人可以進去了。開鎖只有一個步驟那就是開啟鎖,而閉鎖有兩個步驟:一是等待鎖達到開啟狀態,二是獲得鎖並鎖上。顯然,閉鎖的兩個操作應該是原子操作,不能分開

  ② 睡覺與叫醒

  當對方持有鎖時,你就不需要等待鎖變為開啟狀態,而是去睡覺,鎖開啟後對方再來把你叫醒,這是一種典型的生產者消費者模式。用計算機來模擬生產者消費者並不難:一個程序代表生產者,一個程序代表消費者,一片記憶體緩衝區代表商店。生產者將生產的物品從一端放入緩衝區,消費者則從另外一端獲取物品,如下圖所示:

  例如,在.NET中可以通過Monitor.Wait()與Monitor.Pulse()來進行睡覺和叫醒操作:

  首先是消費者執行緒

public void ConsumerDo()
{
    while (true)
    {
        lock(sync)
        {
            // Step1:做一些消費的事情
            ......
            // Step2:喚醒生產者執行緒
            Monitor.Pulse(sync);
            // Step3:釋放鎖並阻止消費者執行緒
            Monitor.Wait(sync);
        }
    }
}

   其次是生產者執行緒

public void ProducerDo()
{
    while (true)
    {
        lock(sync)
        {
            // Step1:做一些生產操作
            ......
            // Step2:喚醒消費者執行緒
            Monitor.Pulse(Dog.lockCommunicate);
            // Step3:釋放鎖並阻止生產者執行緒
            Monitor.Wait(Dog.lockCommunicate);
        }
    }
}

   但是,在此種情形下,生產者和消費者都有可能進入睡覺狀態,從而無法相互叫醒對方而繼續往前推進,也就發生了系統死鎖。如何解決?我們可以用某種方法將發出的訊號累積起來,而不是丟掉。即消費者獲得CPU執行sleep語句後,生產者在這之前傳送的叫醒訊號還保留,因此消費者將馬上獲得這個訊號而醒過來。而能夠將訊號累積起來的作業系統原語就是訊號量。

  (2)訊號量

  訊號量(Semaphore)是一個計數器,其取值為當前累積的訊號數量。它支援兩個操作:加法操作up和減法操作down。執行down減法操作時,請求該訊號量的一個執行緒會被掛起;而執行up加法操作時,會叫醒一個在該訊號量上面等待的執行緒。down和up操作在歷史上被稱為P和V操作,是作業系統中最重要的同步原語的兩個基本操作。

  有些房間,可以同時容納n個人,比如廚房。也就是說,如果人數大於n,多出來的人只能在外面等著。這好比某些記憶體區域,只能供給固定數目的執行緒使用。這時的解決方法,就是在門口掛n把鑰匙。

keys

  進去的人就取一把鑰匙,出來時再把鑰匙掛回原處。後到的人發現鑰匙架空了,就知道必須在門口排隊等著了。這種做法就叫做"訊號量",用來保證多個執行緒不會互相沖突。

  例如,在.NET中提供了一個Semaphore類來進行訊號量操作,下面的示例程式碼演示了4條執行緒想要同時執行ThreadEntry()方法,但同時只允許2條執行緒進入:

    class Program
    {
        // 第一個引數指定當前有多少個“空位”(允許多少條執行緒進入)
        // 第二個引數指定一共有多少個“座位”(最多允許多少個執行緒同時進入)
        static Semaphore sem = new Semaphore(2, 2);

        const int threadSize = 4;

        static void Main(string[] args)
        {
            for (int i = 0; i < threadSize; i++)
            {
                Thread thread = new Thread(ThreadEntry);
                thread.Start(i + 1);
            }

            Console.ReadKey();
        }

        static void ThreadEntry(object id)
        {
            Console.WriteLine("執行緒{0}申請進入本方法", id);
            // WaitOne:如果還有“空位”,則佔位,如果沒有空位,則等待;
            sem.WaitOne();
            Console.WriteLine("執行緒{0}成功進入本方法", id);
            // 模擬執行緒執行了一些操作
            Thread.Sleep(100);
            Console.WriteLine("執行緒{0}執行完畢離開了", id);
            // Release:釋放一個“空位”
            sem.Release();
        }
    }

  如果將資源比作“座位”,Semaphore接收的兩個引數中:第一個引數指定當前有多少個“空位”(允許多少條執行緒進入),第二個引數則指定一共有多少個“座位”(最多允許多少個執行緒同時進入)。WaitOne()方法則表示如果還有“空位”,則佔位,如果沒有空位,則等待;Release()方法則表示釋放一個“空位”。

  不難看出,mutex互斥鎖是semaphore訊號量的一種特殊情況(n=1時)。也就是說,完全可以用後者替代前者。

  但是,如果生產者或消費者將兩個up/down操作順序顛倒,也同樣會產生死鎖。也就是說,使用訊號量原語時,訊號量操作的順序至關重要。那麼,有木有辦法改變這種情況,可不可將訊號量的這些組織工作交給一個專門的構造來負責,解放廣大程式設計師?答案是管程。

  (3)管程

  管程(Monitor)即監視器的意思,它監視的就是程序或執行緒的同步操作。具體來說,管程就是一組子程式、變數和資料結構的組合。言下之意,把需要同步的程式碼用一個管程的構造框起來,即將需要保護的程式碼置於begin monitor和end monitor之間,即可獲得同步保護,也就是任何時候只能有一個執行緒活躍在管程裡面。

  同步操作的保證是由編譯器來執行的,編譯器在看到begin monitor和end monitor時就知道其中的程式碼需要同步保護,在翻譯成低階程式碼時就會將需要的作業系統原語加上,使得兩個執行緒不能同時活躍在同一個管程內。

  例如,在.NET中提供了一個Monitor類,它可以幫我們實現互斥的效果:

    private object locker = new object();
    public void Work()
    {
        // 避免直接使用私有成員locker(直接使用有可能會導致執行緒不安全)
        object temp = locker;
        Monitor.Enter(temp);
        try
        {
            // 做一些需要執行緒同步的工作
        }
        finally
        {
            Monitor.Exit(temp);
        }
    }

  在管程中使用兩種同步機制:鎖用來進行互斥,條件變數用來控制執行順序。從某種意義上來說,管程就是鎖+條件變數

About:條件變數就是執行緒可以在上面等待的東西,而另外一個執行緒則可以通過傳送訊號將在條件變數上的執行緒叫醒。因此,條件變數有點像訊號量,但又不是訊號量,因為不能對其進行up和down操作。

  管程最大的問題就是對編譯器的依賴,因為我們需要將編譯器需要的同步原語加在管程的開始和結尾。此外,管程只能在單臺計算機上發揮作用,如果想在多計算機環境下進行同步,那就需要其他機制了,而這種其他機制就是訊息傳遞。

  (4)訊息傳遞

  訊息傳遞是通過同步雙方經過互相收發訊息來實現,它有兩個基本操作:傳送send和接收receive。他們均是作業系統的系統呼叫,而且既可以是阻塞呼叫,也可以是非阻塞呼叫。而同步需要的是阻塞呼叫,即如果一個執行緒執行receive操作,就必須等待受到訊息後才能返回。也就是說,如果呼叫receive,則該執行緒將掛起,在收到訊息後,才能轉入就緒。

  訊息傳遞最大的問題就是訊息丟失和身份識別。由於網路的不可靠性,訊息在網路間傳輸時丟失的可能性較大。而身份識別是指如何確定收到的訊息就是從目標源發出的。其中,訊息丟失可以通過使用TCP協議減少丟失,但也不是100%可靠。身份識別問題則可以使用諸如數字簽名和加密技術來彌補。

  (5)柵欄

  柵欄顧名思義就是一個障礙,到達柵欄的執行緒必須停止下來,知道出去柵欄後才能往前推進。該院與主要用來對一組執行緒進行協調,因為有時候一組執行緒協同完成一個問題,所以需要所有執行緒都到同一個地方匯合之後一起再向前推進。

  例如,在平行計算時就會遇到這種需求,如下圖所示:

參考資料

鄒恆明,《作業系統之哲學原理》,機械工業出版社

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

執行學習5synchronized 的基礎使用

2018年10月03日 目錄 前言 前言 java中已經有了內建鎖:synchronized,synchronized的特點是使用簡單,一切交給JVM去處理,不需要顯示釋放; j

理解JVMJava記憶體模型執行

Java記憶體模型 JMM(Java Memory Model)是JVM定義的記憶體模型,用來遮蔽各種硬體和作業系統的記憶體訪問差異。 * 主記憶體:所有的變數都儲存在主記憶體(Main Memory,類比實體記憶體)中。 * 工作記憶體:每條執行緒有自己

深度學習框架Keras學習系列線性代數基礎numpy使用Linear Algebra Basis and Numpy

又開一個新坑~~ 因為確實很有必要好好地趁著這個熱潮來研究一下深度學習,畢竟現在深度學習因為其效果突出,熱潮保持高漲不退,上面的政策方面現在也在向人工智慧領域傾斜,但是也有無數一知半解的人跟風吹捧,於是希望藉此教程,讓自己和讀者一起藉助keras,從上到下逐漸

作業系統核心原理-4.執行原理死鎖基礎原理

  我們都見過交通阻塞,一大堆汽車因為爭奪行路權,互不相讓而造成阻塞,又或者因為車輛發生故障拋錨或兩輛車相撞而造成道路阻塞。在這種情況下,所有的車都停下來,誰也無法前行,這就是死鎖。本篇就來了解一下什麼是死鎖,如何應對死鎖。 一、死鎖初窺 1.1 為何會發生死鎖?   死鎖的發生歸根結底是因為對資源的競

作業系統核心原理-4.執行原理執行基礎執行同步

  我們都知道,程序是運轉中的程式,是為了在CPU上實現多道程式設計而發明的一個概念。但是程序在一個時間只能幹一件事情,如果想要同時幹兩件或者多件事情,例如同時看兩場電影,我們自然會想到傳說中的分身術,就像孫悟空那樣可以變出多個真身。雖然我們在現實中無法分身,但程序卻可以辦到,辦法就是執行緒。執行緒就是我們為

Java多執行學習AQS 原理以及 AQS 同步元件總結

常見問題:AQS 原理?;CountDownLatch和CyclicBarrier瞭解嗎,兩者的區別是什麼?用過Semaphore嗎? 本節思維導圖: 阿里雲產品 1888 代金券領取:https://promotion.aliyun.com/ntms

作業系統核心原理-5.記憶體管理基本記憶體管理

  作業系統的兩個角色分別是魔術師和管理者,在管理者這個角色中,除了CPU之外,記憶體是作業系統要管理的另外一個重要資源。記憶體管理需要達到兩個目標:一是地址保護,即一個程式不能訪問另一個程式的地址空間。二是地址獨立,即程式發出的地址應該與物理主存地址無關。這兩個目標就是衡量一個記憶體管理系統是否完善的標準,

2019年面試必備最新Java核心知識點3—JAVA多執行併發

  核心知識——JVM jvm基本概念: JVM 是可執行 Java 程式碼的假想計算機 ,包括一套位元組

Nmap原理02 - 版本探測介紹

掃描 mat pos running style 用戶 tcp連接建立 tin cnblogs Nmap原理02 - 版本探測介紹(上) 1.介紹   本文將介紹如何通過修改或添加nmap-service-probes文件來實現對nmap中未知服務的探測,首先介紹服務和

C# 解決子執行不能操作主執行UI介面問題,涉及到多執行的概念。

1、在預設情況下,C#不准許在一個執行緒中直接訪問或操作另一執行緒中建立的控制元件,這是因為訪問windows窗體控制元件本質上是不安全的。 2、執行緒之間是可以同時執行的,那麼如果有兩個或多個執行緒同時操作某一控制元件的某狀態,嘗試將一個控制元件變為自己需要的狀態時, 執行緒的死鎖就可能發生。

java多執行系列Thread、Runnable、Callable實現多執行的區別

實現多執行緒 java實現多執行緒的方法有三種,分別是繼承thread類,實現runnable介面,實現callable介面(call方法有返回值) /** * 繼承Thread */ public class MyThread extends Thread{ int a = 0;

web安全系列XSS 攻擊基礎原理

跨站指令碼攻擊(XSS)是客戶端指令碼安全的頭號大敵。本文章深入探討 XSS 攻擊原理,下一章(XSS 攻擊進階)將深入討論 XSS 進階攻擊方式。 本系列將持續更新。 XSS 簡介 XSS(Cross Site Script),全稱跨站指令碼攻擊,為了與 CSS(Cascading Style Sheet)

Python爬蟲開發資料儲存以及多執行

0×00 介紹 本文我們就兩個方面來討論如何改進我們的爬蟲:資料儲存和多執行緒,當然我承認這是為我們以後要討論的一些東西做鋪墊。 本人對於Python學習建立了一個小小的學習圈子,為各位提供了一個平臺,大家一起來討論學習Python。歡迎各位到來Python學習群:960410445一起討論

執行學習執行的互斥

生產者與消費者模型 在講同步和互斥之前,首先了解一下消費者模型 什麼是消費者模型? 消費者模型是一個描述消費者和生產者之間的關係的一個模型,生產者和消費者模型指的是在一個場所中,兩個角色,三種關係 消費者和消費者之間——互斥 消費者之間是競爭關係,比如有一個

執行執行者建立一個大小固定的執行執行者

宣告:本文是《 Java 7 Concurrency Cookbook 》的第四章,作者: Javier Fernández González     譯者:許巧輝     校對:方騰飛,葉磊 建立一個大小固定的執行緒執行者 當你使用由Executors類的 newCachedThreadPo

C++ 多執行框架 2Mutex 互斥和 Sem 訊號量

互斥和訊號量是多執行緒程式設計的兩個基礎,其原理就不詳細說了,大家去看看作業系統的書或者網上查查吧。 對於互斥的實現,無論什麼作業系統都離不開三個步驟 1.初始化互斥鎖 2.鎖操作 3.解鎖操作 對於不同的系統只是實現的函式有一些不同而已,但是功能其實都大同小異,在

C++ 多執行框架1new 一下就啟動一個執行

幾年前寫過一個C++的多執行緒框架,雖然寫完了,但是人一懶做了一次說明以後就沒影了,最近把程式碼整理了一下,準備發到github上,在這裡,再把這個框架總結一下吧。 多執行緒一直是程式設計中常見的問題,特別是在Linux的c++上,多執行緒的封裝一直不是很好,當然,

C++ 多執行框架3訊息佇列

之前,多執行緒一些基本的東西,包括執行緒建立,互斥鎖,訊號量,我們都已經封裝,下面來看看訊息佇列 我們儘量少用系統自帶的訊息佇列(比如Linux的sys/msgqueue),那樣移植性不是很強,我們希望的訊息佇列,在訊息打包和提取都是用的標準的C++資料結構,當然,

執行的那些事兒2-- 程序執行的區別聯絡

  在進行多執行緒程式設計之前我們先解決一個基本問題:什麼是執行緒、什麼是程序,他們之間有什麼區別與聯絡。 (1)程序:執行環境                   執行緒:執行單位         用書面一點的知說,程序是一個計算機中程式執行的一個實體,執行緒是作業系

java利用執行ExecutorService配合Callable和Future實現執行方法超時的阻斷

今天在專案開發中需要用到對執行方法加上時間控制,如果方法執行過長則跳出執行,廢話不說,直接上程式碼,用的是執行緒池配合Callable和Future方式對執行方法的超時阻斷。希望各位牛人指正  //啟用執行緒池 final ExecutorServic