多線程介紹

線程和進程

線程和進程的定義

線程:markdown

  • 線程是進程的基本執行單元,一個進程的全部任務都在線程中執行
  • 進程要想執行任務,必須得有線程,進程至少要有一條線程
  • 程序啓動會默認開啓一條線程,這條線程被稱爲主線程或 UI 線程

進程:多線程

  • 進程是指在系統中正在運行的一個應用程序
  • 每一個進程之間是獨立的,每一個進程均運行在其專用的且受保護的內存空間內
  • 經過「活動監視器」能夠查看 Mac 系統中所開啓的進程

進程與線程的關係

地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。 資源擁有:同一進程內的線程共享本進程的資源如內存、I/O、cpu等,可是進程之間的 資源是獨立的。併發

  1. 一個進程崩潰後,在保護模式下不會對其餘進程產生影響,可是一個線程崩潰整個進程都死掉。因此多進程要比多線程健壯。
  2. 進程切換時,消耗的資源大,效率高。因此涉及到頻繁的切換時,使用線程要好於進程。一樣若是要求同時進行而且又要共享某些變量的併發操做,只能用線程不能用進程
  3. 執行過程:每一個獨立的進程有一個程序運行的入口、順序執行序列和程序入口。可是 線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。
  4. 線程是處理器調度的基本單位,可是進程不是。
  5. 線程沒有地址空間,線程包含在進程地址空間中

多線程的意義

優勢:性能

  • 能適當提升程序的執行效率
  • 能適當提升資源的利用率(CPU,內存)
  • 線程上的任務執行完成後,線程會自動銷燬

缺點atom

  • 開啓線程須要佔用必定的內存空間(默認狀況下,每個線程都佔 512 KB)
  • 若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能
  • 線程越多,CPU 在調用線程上的開銷就越大

* 程序設計更加複雜,好比線程間的通訊、多線程的數據共享spa

多線程原理

時間片的概念:CPU在多個任務直接進行快速的切換,這個時間間隔就是時間片線程

* (單核CPU)同一時間,CPU 只能處理 1 個線程  * 換言之,同一時間只有 1 個線程在執行設計

* 多線程同時執行: * 是 CPU 快速的在多個線程之間的切換3d

  • CPU 調度線程的時間足夠快,就形成了多線程的「同時」執行的效果

* 若是線程數很是多code

  • CPU 會在 N 個線程之間切換,消耗大量的 CPU 資源 

* 每一個線程被調度的次數會下降,線程的執行效率下降

多線程技術方案

image.png

C與OC的橋接

  • __bridge只作類型轉換,可是不修改對象(內存)管理權;
  • __bridge_retained(也可使用CFBridgingRetain)將Objective-C的對象轉換爲 Core Foundation的對象,同時將對象(內存)的管理權交給咱們,後續須要使用 CFRelease或者相關方法來釋放對象;
  • __bridge_transfer(也可使用CFBridgingRelease)將Core Foundation的對象 轉換爲Objective-C的對象,同時將對象(內存)的管理權交給ARC。

線程生命週期

image.png

  • 新建:new新建線程後,調用start後,並不會當即執行,而是進入就緒狀態,等待CPU的調度。
  • 運行:CPU調度當前線程,進入運行狀態,開始執行任務。若是當前線程還在運行中,CPU從可調度池中調用其餘線程,來執行此任務。
  • 阻塞:運行中的任務,被調用sleep/等待同步鎖時,會進入阻塞狀態。全部線程都中止,等待sleep結束/獲取同步鎖,纔會回到就緒狀態。
  • 死亡:運行中的任務,在任務執行完或被強制退出時,線程自動進入Dead銷燬。

線程池

image.png

飽和策略

  • AbortPolicy 直接拋出RejectedExecutionExeception異常來阻止系統正常運行
  • CallerRunsPolicy 將任務回退到調用者
  • DisOldestPolicy 丟掉等待最久的任務
  • DisCardPolicy 直接丟棄任務

這四種拒絕策略均實現的RejectedExecutionHandler接口

多線程常見問題

  1. 任務執行速度的影響因素有哪些
  • cup 的調度狀況
  • 任務的複雜度
  • 優先級
  • 線程狀態
  1. 優先級翻轉

在看優先級反轉前先了解什麼是IO密集型線程和CPU密集型線程。

  • IO密集型線程:頻繁等待的線程,等待的時候會讓出時間片。
  • CPU密集型線程:不多等待的線程,意味着長時間佔用着 CPU。

IO密集型線程比CPU密集型線程更容易獲得優先級提高。

特殊場景下,當多個優先級都比較高的CPU 密集型線程霸佔了全部 CPU 資源,而此時優先級較低的 IO 密集型線程將持續等待,產生線程餓死的現象。固然,爲了不線程餓死,CPU會發揮調度做用去逐步提升被「冷落」線程的優先級(提升優先級不必定就會執行,有機率的問題),IO 密集型線程一般狀況下比 CPU 密集型線程更容易獲取到優先級提高。

  1. 線程優先級影響因素
  • 用戶指定
  • 根據等待的頻繁度提高或者下降
  • 長時間不執行會被提升優先級

自旋鎖和互斥鎖

在咱們使用多線程的過程當中會遇到一種現象,就是資源搶奪。

image.png

例如多窗口售票這個案例,假設如今有 1000 張票,窗口 1 售賣了一張還剩 999 張,可是窗口 2 並不能同步知道剩餘票數,因此仍是按照 1000 張去售票,這種狀況就會出現問題。這個時候咱們就須要藉助加鎖操做來解決這種問題。這裏咱們介紹兩種鎖,自旋鎖與互斥鎖。

互斥鎖:

  • 發現其餘線程執行,當前線程休眠 (就緒狀態) 一直在等打開 , 喚醒執行。
  • 保證鎖內代碼,同一時間,只有一條線程可以執行。
  • 互斥鎖的鎖定範圍,應該儘可能小,鎖定範圍越大,效率越差。

互斥鎖參數:

  • 可以加鎖的任意NSObject對象。
  • 鎖對象要保證全部線程都可以訪問。
  • 若是代碼只有一個地方須要加鎖,大多都使用self,這樣能夠避免單獨再建立一個鎖對象。

自旋鎖:

  • 發現其餘線程執行,當前線程詢問 , 忙等,耗費性能比較高。自旋鎖內容應儘量小,保障儘快完成鎖內任務。

互斥鎖與自旋鎖的區別:

  • 互斥鎖是被動等待代碼觸發,再上鎖。被動觸發,任務資源較大(執行耗時長)時,選擇互斥鎖。
  • 自旋鎖是主動輪循請求資源。因此自旋鎖更消耗資源。要求當即執行,任務資源較小(執行耗時短)時,可選擇自旋鎖。

atomic 與 nonatomic

atomic:

  • atomic 是原子屬性,是爲多線程開發準備的,是默認屬性!
  • 僅僅在屬性的 setter 方法中,增長了鎖(自旋鎖),可以保證同一時間,只有一條線程對屬性進行操做
  • 同一時間 單(線程)寫多(線程)讀的線程處理技術

nonatomic:

  • nonatomic 是非原子屬性
  • 沒有鎖!性能高!

下面咱們來看一下 atomic 的底層實現:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    // 判斷是不是 atomic 標識,是的話就添加鎖操做
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
複製代碼

在源碼中咱們能夠看到 atomic 其實就是一個標識,底層根據 atomic 標識來判斷是否加鎖。

相關文章
相關標籤/搜索