Atomic原子操作原理剖析

来源:https://www.cnblogs.com/vanch/archive/2018/12/28/10192002.html
-Advertisement-
Play Games

前言 絕大部分 程式員使用屬性時,都不太關註一個特殊的修飾首碼,一般都無腦的使用其非預設預設的狀態,他就是 。 入門教程中一般都建議使用非原子操作,因為新手大部分操作都在主線程,用不到線程安全的特性,大量使用還會降低執行效率。 那他到底怎麼實現線程安全的呢?使用了哪種技術呢? 原理 屬性的實現 首先 ...


前言

絕大部分 Objective-C 程式員使用屬性時,都不太關註一個特殊的修飾首碼,一般都無腦的使用其非預設預設的狀態,他就是 atomic

@interface PropertyClass

@property (atomic, strong)    NSObject *atomicObj;  //預設也是atomic
@property (nonatomic, strong) NSObject *nonatomicObj;

@end

入門教程中一般都建議使用非原子操作,因為新手大部分操作都在主線程,用不到線程安全的特性,大量使用還會降低執行效率。

那他到底怎麼實現線程安全的呢?使用了哪種技術呢?


原理

屬性的實現

首先我們研究一下屬性包含的內容。通過查閱源碼,其結構如下:

struct property_t {
    const char *name;       //名字
    const char *attributes; //特性
};

屬性的結構比較簡單,包含了固定的名字和元素,可以通過 property_getName 獲取屬性名,property_getAttributes 獲取特性。

上例中 atomicObj 的特性為 T@"NSObject",&,V_atomicObj,其中 V 代表了 strongatomic 特性預設沒有顯示,如果是 nonatomic 則顯示 N

那到底是怎麼實現原子操作的呢? 通過引入runtime,我們能調試一下調用的函數棧。

可以看到在編譯時就把屬性特性考慮進去了,Setter 方法直接調用了 objc_setPropertyatomic 版本。這裡不用 runtime 去動態分析特性,應該是對執行性能的考慮。

static inline void reallySetProperty(id self, SEL _cmd, 
    id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
    //偏移為0說明改的是isa
    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);
    }
    //判斷原子性
    if (!atomic) {
        //非原子直接賦值
        oldValue = *slot;
        *slot = newValue;
    } else {
        //原子操作使用自旋鎖
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    // 取isa
    if (offset == 0) {
        return object_getClass(self);
    }

    // 非原子操作直接返回
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // 原子操作自旋鎖
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // 出於性能考慮,在鎖之外autorelease
    return objc_autoreleaseReturnValue(value);
}

什麼是自旋鎖呢?

鎖用於解決線程爭奪資源的問題,一般分為兩種,自旋鎖(spin)和互斥鎖(mutex)。

互斥鎖可以解釋為線程獲取鎖,發現鎖被占用,就向系統申請鎖空閑時喚醒他並立刻休眠。

自旋鎖比較簡單,當線程發現鎖被占用時,會不斷迴圈判斷鎖的狀態,直到獲取。

原子操作的顆粒度最小,只限於讀寫,對於性能的要求很高,如果使用了互斥鎖勢必在切換線程上耗費大量資源。相比之下,由於讀寫操作耗時比較小,能夠在一個時間片內完成,自旋更適合這個場景。

自旋鎖的坑

但是iOS 10之後,蘋果因為一個巨大的缺陷棄用了 OSSpinLock 改為新的 os_unfair_lock

新版 iOS 中,系統維護了 5 個不同的線程優先順序/QoS: background,utility,default,user-initiated,user-interactive。高優先順序線程始終會在低優先順序線程前執行,一個線程不會受到比它更低優先順序線程的干擾。這種線程調度演算法會產生潛在的優先順序反轉問題,從而破壞了 spin lock。

描述引用自 ibireme 大神的文章。

我的理解是,當低優先順序線程獲取了鎖,高優先順序線程訪問時陷入忙等狀態,由於是迴圈調用,所以占用了系統調度資源,導致低優先順序線程遲遲不能處理資源並釋放鎖,導致陷入死鎖。

那為什麼原子操作用的還是 spinlock_t 呢?

using spinlock_t = mutex_tt<LOCKDEBUG>;
using mutex_t = mutex_tt<LOCKDEBUG>;

class mutex_tt : nocopy_t {
    os_unfair_lock mLock; //處理了優先順序的互斥鎖
    void lock() {
        lockdebug_mutex_lock(this);
        os_unfair_lock_lock_with_options_inline
            (&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION);
    }
    void unlock() {
        lockdebug_mutex_unlock(this);
        os_unfair_lock_unlock_inline(&mLock);
    }
}

差點被蘋果騙了!原來系統中自旋鎖已經全部改為互斥鎖實現了,只是名稱一直沒有更改。

為了修複優先順序反轉的問題,蘋果也只能放棄使用自旋鎖,改用優化了性能的 os_unfair_lock,實際測試兩者的效率差不多。


問答

atomic的實現機制

使用atomic 修飾屬性,編譯器會設置預設讀寫方法為原子讀寫,並使用互斥鎖添加保護。

為什麼不能保證絕對的線程安全?

單獨的原子操作絕對是線程安全的,但是組合一起的操作就不能保證。

- (void)competition {
    self.intSource = 0;

    dispatch_async(queue1, ^{
      for (int i = 0; i < 10000; i++) {
          self.intSource = self.intSource + 1;
      }
    });

    dispatch_async(queue2, ^{
      for (int i = 0; i < 10000; i++) {
          self.intSource = self.intSource + 1;
      }
    });
}

最終得到的結果肯定小於20000。當獲取值的時候都是原子線程安全操作,比如兩個線程依序獲取了當前值 0,於是分別增量後變為了 1,所以兩個隊列依序寫入值都是 1,所以不是線程安全的。

解決的辦法應該是增加顆粒度,將讀寫兩個操作合併為一個原子操作,從而解決寫入過期數據的問題。

os_unfair_lock_t unfairLock;
- (void)competition {
    self.intSource = 0;

    unfairLock = &(OS_UNFAIR_LOCK_INIT);
    dispatch_async(queue1, ^{
      for (int i = 0; i < 10000; i++) {
          os_unfair_lock_lock(unfairLock);
          self.intSource = self.intSource + 1;
          os_unfair_lock_unlock(unfairLock);
      }
    });

    dispatch_async(queue2, ^{
      for (int i = 0; i < 10000; i++) {
          os_unfair_lock_lock(unfairLock);
          self.intSource = self.intSource + 1;
          os_unfair_lock_unlock(unfairLock);
      }
    });
}

總結

通過學習屬性的原子性,對系統中鎖的理解又加深,包括自旋鎖,互斥鎖,讀寫鎖等。

本來都以為實現是自旋鎖了,還好留了個心眼多看了一層才發現最終實現還是互斥鎖。這件事也給我一個小教訓,查閱源碼還是要刨根問底,只浮於錶面的話,可能得不到想要的真相。

引用

可以編譯的runtime庫

不再安全的 OSSpinLock


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

-Advertisement-
Play Games
更多相關文章
  • 正常切換切換前: 主庫:SQL> select DATABASE_ROLE from v$database;DATABASE_ROLE PRIMARY SQL> select OPEN_MODE,PROTECTION_MODE,PROTECTION_LEVEL,SWITCHOVER_STATUS f ...
  • SELECT子句:用來指定查詢返回欄位,星號(*)表示返回所有欄位 SELECT [DISTINCT]*|欄位列表 #DISTINCT 用來過濾重覆數據 FROM子句:用來指定數據來源的表 FROM <表名> WHERE子句:用來定義查詢返回數據的條件 WHERE 查詢條件 GROUP BY子句:用 ...
  • 配合食用:http://www.runoob.com/mysql/mysql-data-types.html 一.數據類型 1、整型 取值範圍如果加了 unsigned,則最大值翻倍,如 tinyint unsigned 的取值範圍為(0~256)。 int(m) 里的 m 是表示 SELECT 查 ...
  • 以下是根據工作中遇到各種場景用到的一些Mysql用法,比較實用,基本是語法之外的一些東西。 以下是根據工作中遇到各種場景用到的一些Mysql用法,比較實用,基本是語法之外的一些東西。 修改賬戶密碼 1.打開Mysql控制台,輸入原密碼; 2.輸入以下語法:mysql> set password fo ...
  • CREATE TABLE [StudentScores] ( [UserName] NVARCHAR(20), --學生姓名 [Subject] NVARCHAR(30), --科目 [Score] FLOAT, --成績 ) INSERT INTO [StudentScores] SELECT '... ...
  • 這個案例是前兩天出現的,一直沒有時間總結,25號凌晨4點去處理資料庫的故障問題。遠程連上公司的區域網,psping檢查發現伺服器的1433埠不通,資料庫連接不上,但是主機又能ping通,登錄伺服器檢查發現SQL Server的SQL Server (MSSQLSERVER) Service 等服務... ...
  • olsnodes -n 查看節點個數 crs_stat -t 查看RAC中各節點的資源狀態 crs_stat -p 查看RAC的節點的配置 crsctl命令: 對於crsctl命令不清楚的,在終端輸入crsctl,他會有命令的介紹及補充,很直觀詳細的。 crs常用命令: crsctl stop cr ...
  • 本文由雲+社區發表 本文作者:許中清,騰訊雲自研資料庫CynosDB的分散式存儲CynosStore負責人。從事資料庫內核開發、資料庫產品架構和規劃。曾就職於華為,2015年加入騰訊,參與過TBase(PGXZ)、CynosDB等資料庫產品研發。專註於關係資料庫、資料庫集群、新型資料庫架構等領域。目 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...