1. 程式人生 > >Coffee's Coding Blog

Coffee's Coding Blog

執行緒鎖 Lock

多執行緒和多程序最大的不同在於,多程序中,同一個變數,各自有一份拷貝存在於每個程序中,互不影響,而多執行緒中,所有變數都由所有執行緒共享,所以,任何一個變數都可以被任何一個執行緒修改,因此,執行緒之間共享資料最大的危險在於多個執行緒同時改一個變數,把內容給改亂了。

先看一個沒有加鎖的操作
balance = 0
def change_it_without_lock(n):
    global balance
    # 不加鎖的話 最後的值不是0
    # 執行緒共享資料危險在於 多個執行緒同時改同一個變數
    # 如果每個執行緒按順序執行,那麼值會是0, 但是執行緒時系統排程,又不確定性,交替進行
    # 沒鎖的話,同時修改變數
    # 所以加鎖是為了同時只有一個執行緒再修改,別的執行緒表一定不能改
    for i in range(100000):
        balance = balance + n
        balance = balance - n

    print balance

threads = [
    threading.Thread(target=change_it_without_lock, args=(8,) ),
    threading.Thread(target=change_it_without_lock, args=(10,) )
]

[t.start() for t in threads]
[t.join() for t in threads]

print balance

我們定義了一個共享變數balance,初始值為0,並且啟動兩個執行緒,先存後取,理論上結果應該為0
但是,由於執行緒的排程是由作業系統決定的,當t1、t2交替執行時,只要迴圈次數足夠多,balance的結果就不一定是0了。

按照一個執行緒的思想, balance無論處理迴圈多少次都是等於0的,然而開了2個執行緒最後結果卻不是0,那是因為兩個執行緒交換進行,操作沒有原子性,比如,
執行緒1進行到 balance=balance + 8 == 0 + 8 = 8
此時切換到執行緒2,執行緒2拿到的balance=8, 進行balance = 8 + 10 = 18,
此時切換到前程1 ,balance = balance - 8 = 18-8=10 ! = 0
執行緒1的結果已經不等0,之後的結果也會亂套,就是因為操作沒有原子性,
所以我們多執行緒操作同一個資源(這裡就是全域性變數balance)的時候,需要加鎖

加鎖修改後,程式碼

def change_it_with_lock(n):
    global balance
    if lock.acquire():
        try
            for i in range(100000):
                balance = balance + n
                balance = balance - n
        # 這裡的finally 防止中途出錯了,也能釋放鎖
        finally:
            lock.release()
threads = [
    threading.Thread(target=change_it_without_lock, args=(8,) ),
    threading.Thread(target=change_it_without_lock, args=(10,) )
]
lock = threading.Lock()
[t.start() for t in threads]
[t.join() for t in threads]

print balance
# 這裡的結果一定是0

執行緒死鎖

執行緒的一大問題就是通過加鎖來”搶奪“共享資源的時候有可能造成死鎖,例如下面的程式:

import threading
import os
import time
import random

balance=0
def change_it(n, lock):
    global balance
    threading_name = threading.currentThread().name
    if lock.acquire():
        try:
            for i in range(1000000):
                balance = balance + n
                balance = balance - n
                #time.sleep(random.random())
                # 中途錯誤,會死鎖,所以應該包個try finally 釋放鎖
                # 假設執行緒1中途錯誤, 沒有釋放鎖
                if i == 200 and threading_name == 'Thread-1':
                    print 'set error', threading_name
                    raise Exception('hah')

            #  迴圈完釋放鎖, 但是執行緒1來不到這裡 已經出錯了
            lock.release()
        except Exception, e:
            print 'error', threading_name
            pass
        finally:
            pass
            print 'release lock', threading_name
            print 'i is ',  i, threading_name
            # 這裡註釋了, 最安全的做法應該釋放鎖是放在這裡的, 我們為了演示死鎖,註釋掉
            #lock.release()
lock2 = threading.Lock()

t1 = threading.Thread(target=change_it, args=(5, lock2))
t2 = threading.Thread(target=change_it, args=(5, lock2))
t1.start()
t2.start()
t1.join()
t2.join()
print balance

還有一種死鎖不是由於內部錯誤,沒有合理釋放鎖導致的, 就是兩個執行緒都在等待對方的鎖,看下面程式碼:

from threading import Lock  
_base_lock = Lock()  
_pos_lock  = Lock()  
_base = 1

def _sum(x, y):  
    # Time 1
    with _base_lock:
        # Time 3
        with _pos_lock:
            result = x + y
    return result
def _minus(x, y):  
    # Time 0
    with _pos_lock:
        # Time 2
        with _base_lock:
            result = x - y
    return result

由於執行緒的排程執行順序是不確定的,在執行上面兩個執行緒 _sum/_minus
的時候就有可能出現註釋中所標註的時間順序,即 # Time 0
的時候執行到 with _pos_lock
獲取了_pos_lock 鎖,
而接下來由於阻塞馬上切換到了 _sum 中的 # Time 1,並獲取了_base_lock
,接下來由於兩個執行緒互相鎖定了彼此需要的下一個鎖,將會導致死鎖,即程式無法繼續執行。根據 我是一個執行緒 中所描述的,為了避免死鎖,需要所有的執行緒按照指定的演算法(或優先順序)來進行加鎖操作。不管怎麼說,死鎖問題都是一件非常傷腦筋的事,原因之一在於不管執行緒實現的是併發還是並行,在程式設計模型和語法上看起來都是並行的,而我們的大腦雖然是一個(內隱的)絕對並行加工的機器,卻非常不善於將並行過程具象化(至少在未經足夠訓練的時候)。而與執行緒相比,協程(尤其是結合事件迴圈)無論在程式設計模型還是語法上,看起來都是非常友好的單執行緒同步過程。

相關推薦

Coffee's Coding Blog

執行緒鎖 Lock 多執行緒和多程序最大的不同在於,多程序中,同一個變數,各自有一份拷貝存在於每個程序中,互不影響,而多執行緒中,所有變數都由所有執行緒共享,所以,任何一個變數都可以被任何一個執行緒修改,因此,執行緒之間共享資料最大的危險在於多個執行緒同時改一個變數,把內容給改亂了。 先

經典SQL語句大全-【轉載自】部落格園,作者部落格:YuBinfeng's Technology Blog

因最近學習MySQL,閒來無事逛帖子時,發現這篇較為經典的部落格,特轉載以防備用學習,同時希望也可以幫到他人,廢話不多說,進入正文  一、基礎 1、說明:建立資料庫 CREATE DATABASE database-name 2、說明:刪除資料庫 drop data

Let's coding world

1. 長度限制 <p>1. 長度限制</p> <form name=a onsubmit="return test()">  <textarea name="b" cols="40" rows="6" placehold

Charles Ren's Tech Blog

以下主要記錄一些個人認為比較重要的點,而不是全部詳細的JS內容,由於看的是英文版的教程詳情參考w3school,以及codemosh的視訊教程,這個是付費的。 javascript可以執行在瀏覽器,在瀏覽器外執行在Node環境上。 Js basic JS中ES

end's coding life

一、事務 定義 事務提供一種機制將一個活動涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作只有在所有操作均能正常執行的情況下方能提交,只要其中任一操作執行失敗,都將導致整個事務的回

Rare's Tech-Blog

在go的pkg庫裡提供了一個單元測試的框架testing,並提供了一個測試工具gotest 規則如下: 如果我們有一個檔案add.go,這其對應的測試檔名為add_test.go 在add_test.go中,引入testing庫,然後新增測試方法 測試方法的函式必須是如下形

記錄一段生成素數python程式碼的調優過程 • cenalulu's Tech Blog

簡介:本文主要記錄了博主對一段使用python實現的素數生成程式碼的不斷優化過程。 背景:最近在刷Project Euler的題目,刷到第十題(計算2百萬以下素數的和)的時候發現之前的素數生成程式碼效率太低導致幾分鐘都出不來。於是通過不斷的調優,終於得到一個能在秒級算出2百萬以內的素

全域性唯一ID生成方案對比 • cenalulu's Tech Blog

彙總了各大公司的全域性唯一ID生成方案,並做了一個簡單的優劣比較 背景:在實現大型分散式程式時,通常會有全域性唯一ID(也成GUID)生成的需求,用來對每一個物件標識一個代號。本文就列舉了博主收集的各種全域性唯一ID生成的方案,做一個簡單的類比和備忘。 GUID的基本需求 一

為Bash和VIM配置一個美觀奢華的狀態提示欄 • cenalulu's Tech Blog

本文將詳細介紹在Mac環境下安裝powerline的方式 什麼是powerline 如果你不是通過搜尋引擎搜到這篇文章的話,也許你還沒有聽說過powerline。而你又恰巧是個*UNIX黨,或者VIM黨的話,那麼強烈建議你瞭解並使用powerline。powerline是一個statel

MySQL入門書籍和方法分享 • cenalulu's Tech Blog

本文羅列了一些適用於MySQL及運維入門和進階使用的書籍。 背景:各大論壇上總是有很多同學諮詢想學習資料庫,或者是為入行DBA做些準備。幾年來作為一個MySQL DBA的成長過程有一些積累和感悟,特此拿出來和大家分享。 申明:本篇部落格原來對每一本書都附上了ppurl的下載

為什麼陣列標號是從0開始的 • cenalulu's Tech Blog

本文通過彙總一些網上搜集到的資料,總結出大部分程式語言中陣列下標從0開始的原因 背景 我們知道大部分程式語言中的陣列都是從0開始編號的,即array[0]是陣列的第一個元素。這個和我們平時生活中從1開始編號的習慣相比顯得很反人類。那麼究竟是什麼樣的原因讓大部分程式語言陣列都遵從了這個

關於Relay Log無法自動刪除的問題 • cenalulu's Tech Blog

本文介紹了一次運維實踐中relay-log長期無法自動刪除的原因和解決過程 背景: 今天在運維一個mysql例項時,發現其資料目錄下的relay-log 長期沒有刪除,已經堆積了幾十個relay-log。 然而其他作為Slave伺服器例項卻沒有這種情況。 現象分析 通過收集到的

Tock策略簡介 • cenalulu's Tech Blog

背景:最近在定位一次硬體升級對資料庫效能影響的故障的時候看了挺多Intel的白皮書。其中Intel的tic-toc策略(又稱為:tick-tock)很有趣,特別幾張roadmap畫得很贊。因此摘錄在此,已做備忘。 什麼是tic-toc tic-toc是Intel從2008年引入的一個C

MySQL timeout相關引數解析和測試 • cenalulu's Tech Blog

MySQL中有兩個關於連線超時的配置項: wait_timeout和interactive_timeout。他們之間在某些條件下會互相繼承,那究竟這兩個引數會在什麼情況下起作用呢? 本文將會通過一些測試例項來證明總結兩者的相互關係。 引數介紹 interactive_timeout

Innodb單表資料物理恢復 • cenalulu's Tech Blog

本文將介紹使用物理備份恢復Innodb單表資料的方法 前言: 隨著innodb的普及,innobackup也成為了主流備份方式。物理備份對於新建slave,全庫恢復的需求都能從容應對。 但當面臨單表資料誤刪,或者單表誤drop的情況,如果使用物理全備進行恢復呢? 下文將進行詳

10分鐘學會理解和解決MySQL亂碼問題 • cenalulu's Tech Blog

本文將詳細介紹MySQL亂碼的成因和具體的解決方案 在閱讀本文之前,強烈建議對字符集編碼概念還比較模糊的同學 閱讀下博主之前對相關概念的一篇科普:十分鐘搞清字符集和字元編碼 MySQL出現亂碼的原因 要了解為什麼會出現亂碼,我們就先要理解:從客戶端發起請求,到MySQL儲存資料

MySQL Backup in Facebook • cenalulu's Tech Blog

本文將較為詳細的介紹Facebook對於MySQL資料庫的備份策略和方法 準備知識 在進入詳細介紹之前,先簡要介紹一些Facebook相關的架構關鍵字 python Facebook幾乎所有的資料庫自動化運維繫統都是通過python實現的,所有可文件化的手工操作都有

MySQL中行列轉換的SQL技巧 • cenalulu's Tech Blog

詳細介紹MySQL中用SQL實現行列轉換的技巧 行列轉換常見場景 由於很多業務表因為歷史原因或者效能原因,都使用了違反第一正規化的設計模式。即同一個列中儲存了多個屬性值(具體結構見下表)。 這種模式下,應用常常需要將這個列依據分隔符進行分割,並得到列轉行的結果。 表資料:

Latest Posts • cenalulu's Tech Blog

July 18, 2015 前言:博主在剛接觸Python的時候時常聽到GIL這個詞,並且發現這個詞經常和Python無法高效的實現多執行緒劃上等號。本著不光要知其然,還要知其所以然的研究態度,博主蒐集了各方面的資料,花了一週內幾個小時的閒暇時間深入理解了下GIL,並歸納

Project Euler第一階段刷題總結 • cenalulu's Tech Blog

本文是博主對於用Python進行Project Euler第一階段刷題的總結和一些感悟 背景:為了更好的學習Python,博主從2015-01-01開始進行Project Euler的刷題。至今已經完成了兩個階段(50題)。下面是兩階段以來的一些經驗和感悟 Project E