天天看點

線程安全(互斥鎖)

線程安全 (這些接口都是C庫實作的)
    黃牛搶票(黃牛---線程, 票 --- 臨界資源) 兩個黃牛搶到同一張票, 
    1. 線程安全指的是多個線程同時運作通路臨界資源, 不會導緻程式的結果産生二義性,
        臨界資源: 在同一時刻, 該資源隻能被一個執行流所通路, 涉及臨界資源的區域 --> 臨界區
        通路: 在臨界區當中對臨界資源進行非原子操作,意味着可以被打斷
        (原子操作是一步完成的,目前操作隻有兩個結果, 要不完成, 要不未開始)
    2. 如何保證線程安全
        互斥: 保證在同一時刻隻有一個執行流通路臨界資源
            互斥鎖:  (這個鎖是在堆上開辟的, 而這個堆也是在PCB中的 屬于task_struct結構體中 虛拟位址空間的堆)
  !!!!!!    一定要在建立線程之前去初始化互斥鎖, 這樣就可以線上程内部使用互斥鎖了, 當線程退出後, 需要釋放互斥鎖資源
  !!!!!!    加鎖一定是要在通路臨界資源之前加的,  拿到鎖資源之後, 再去通路臨界資源

                1. 使用互斥鎖來保證互斥屬性   (他維護他自己的計數器)
                2. 互斥鎖本質上是一個計數器, 但是這個計數器隻有兩個取值, 一個為0,一個為1,
                    0代表: 無法擷取互斥鎖
                    1代表: 可以獲得互斥鎖
                3 在通路臨界資源前, 先擷取互斥鎖 
                    如果能夠擷取到互斥鎖, 表示目前資源可以去通路
                    如果不能擷取到互斥鎖, 表示目前資源不可以去通路, 也就是意味着計數器當中的值為0, 意味着已經有一個線程正在通路臨界資源
                4. 使用流程:
                    1. 初始化互斥鎖
                    2. 加鎖(将計數器當中的值變成0)
                        如果計數器為1, 可以正常加鎖
                        如果計數器為0, 不可以正常加鎖, 目前想要加鎖的執行流被阻塞
                    3. 通路臨界資源
                    4. 解鎖(将計數器當中的值變成1)
                5. 操作接口
                    1. 定義互斥鎖
                        pthread_mutex_t:互斥鎖變量類型
                    2. 初始化互斥鎖
                        int pthread_mutex_init(pthread_mutex_t* mutex, pthread_mutexattr_t* attr)  (動态初始化)
                            mutex: 互斥鎖變量, 要初始化哪一個互斥鎖變量, 一般情況下, 定義互斥鎖對象, 傳入互斥鎖對象的位址
                            attr: 互斥鎖的屬性, 一般情況下, 我們直接置為NULL, 采用預設屬性
                    課後調研: 為什麼可以直接這樣指派就初始化了????????????????? 檢視源碼 檢視定義
                    還有一種初始化方式為指派初始化
                    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER(也叫靜态初始化)
                    3. 加鎖
                        int pthread_mutex_lock(pthread_mutex_t* mutex) ----> 阻塞加鎖操作
                            mutex: 要對哪一個互斥鎖進行加鎖, 傳遞互斥鎖對象的位址即可
                            該接口為阻塞接口, 如果加鎖成功則傳回, 如果加鎖失敗, 直到加鎖成功

                        這是一個帶有逾時的加鎖接口                 
                        int pthread_mutex_timedlock(pthread_mutex_t* mutex, const struct timespec* abs_timeout)
                            mutex: 要對哪一個互斥鎖進行加鎖, 傳遞互斥鎖對象的位址即可
                            abs_timeout: 加鎖逾時時間, 在加鎖的時候, 如果超過逾時時間還沒有加上鎖, 則直接傳回, 會報錯, 報錯TIMEOUT, 不會進行阻塞等待
                            struct timespec: 有兩成員, 第一個變量為秒級, 第二個為納秒

                        int pthread_mutex_trylock(pthread_mutex_t* mutex)   ----> 非阻塞加鎖操作
                            mutex: 傳入互斥鎖的位址, 來進行加鎖操作
                                如果計數器值為1, 意味着可以加鎖, 加鎖操作後, 對計數器當中隻從1改變成為0
                                如果計數器值為0, 一位這不可以加鎖, 該接口直接傳回, 不進行阻塞等待, 傳回EBUSY, 表示拿不到加鎖資源

                    4. 解鎖操作:
                        int pthread_mutex_unlock(pthread_mutex_t* mutex)
                            不管使用pthread_mutex_lock/pthread_mutex_trylock/pthread_mutex_timedlock三種哪種接口加鎖, 都可以使用unlock接口都可以解鎖
                    如果不解鎖, 導緻阻塞等待鎖資源 , 不解鎖就會使執行流陷入阻塞, 在二次對互斥鎖加鎖時, owner線程也進入等待
                        thread apply all bt:  調試的時候使用這個指令 可以檢視所有線程的調用堆棧
                    如果不釋放互斥鎖, 則會阻塞其他線程, 我們稱之為死鎖
                    互斥鎖隻能被自己線程(誰拿到鎖資源)釋放, 别的線程都釋放不了
                        什麼時候解鎖呢???????????????????????????????????????????????????????
                           若隻在入口函數退出是加入解鎖, 導緻線程用完互斥鎖之後, 程式執行後, 第一個工作線程把鎖拿走了, 未解鎖, 且退出了(類似偷走了), 其他線程進行不了解鎖操作, 計數器的值依舊為0, 導緻阻塞
                        是以應在所有可能退出的地方都要加上解鎖操作,
                    5. 銷毀互斥鎖變量
                        int pthread_mutex_destory(pthread_mutex_t* mutex)
                    CPU是作業系統去管理的, 其他程式分時間在CPU上運作

                問題: 對于加鎖操作, 将計數器當中的值從1變成0, 為什麼是原子操作?
                    這個互斥鎖的計數器本身就是C庫在維護的, 計數器本身也是一個變量, 這個變量儲存了1/0; 根據馮諾依曼體系結構, 記憶體和CPU, 寄存器的關系, 進行加鎖時 需要CPU去判斷是否可以加鎖, 計數器(mutex_val)的值儲存在記憶體中, 不管計數器的值是怎樣, 隻有加鎖都給寄存器中放一個0, 加鎖時交換寄存器與記憶體的值, xchgb(彙編中的指令), 這個交換操作是原子性的, 在交換結束後, 接下來隻需要去
                    判斷寄存器中的值是否為1, 來判斷是否可以加鎖, 
                    如果寄存器中的值為1, 則表示之前記憶體中計數器的值為1, 表示可以加鎖, 
                    如果為0, 則表示之前記憶體中計數器的值為0, 表示不可以加鎖; 
                    主要是由于這個交換操作保證了原子性.

                    上鎖: 第一步是将寄存器的值變為0, 第二步将寄存器當中的值與記憶體的值交換, 接着判斷寄存器當中的值是否大于0, 如果大于0 , 直接傳回, 說明lock這個函數傳回了, 當等于0時 進行上鎖, 當寄存器中的值為0時, 上鎖成功
                    解鎖時, 将記憶體中計數器的值變為1 (每句彙編代碼都保證了原子性, 但是我們所寫的指令則可能是多條彙編代碼, 而被排程的時候是一條指令還執行完, 傳回時從上次中斷的彙編代碼的下一條代碼開始 恢複現場)
                    
            死鎖: 
                1. 什麼是死鎖?
                    程式當中有喲個執行流沒有釋放鎖資源, 會導緻其他想要擷取該所的執行流陷入阻塞等待, 這種情況被稱為死鎖
                    程式當中每一個執行流都占有一把互斥鎖, 但是由于各個執行流在占有互斥鎖(已經擷取)的情況下, 還想申請對方的鎖(其他執行流所占有的互斥鎖), 這種也會導緻死鎖  ------ 吃着碗裡的,看着鍋裡的, 誰申請誰阻塞


        條件變量 --> 同步: 保證程式對臨界資源通路的合理性

        信号量 --> 既能保證同步 也能保證互斥
           

繼續閱讀