1. 程式人生 > >11.python並發入門(part3 多線程與互斥鎖)

11.python並發入門(part3 多線程與互斥鎖)

python 線程 lock 互斥鎖

一、鎖的概念。

鎖,通常被用來實現共享數據的訪問,為每一個共享的數據,創建一個Lock對象(一把鎖),當需要訪問這個共享的資源時,可以調用acquire方法來獲取一個鎖的對象,當共享資源訪問結束後,在調用release方法去解鎖。


二、python中的互斥鎖。

在介紹互斥鎖之前,先來一起看一個例子。(每個線程對num實現一次-1的操作)

import threading

import time

num = 200 #每個線程都共享這個變量。

tread_list = []

def count_num():

global num #每個線程都去獲取這個全局變量。

temp_num = num

time.sleep(0.1) #執行sleep,相當於執行IO操作.

num = temp_num - 1 #對公共的變量做一個-1的操作。

for i in range(200): #同時開啟200個線程

t = threading.Thread(target=add_num)

t.start()

tread_list.append(t)

for t in tread_list:

t.join()

print "ending....num = %s" %(num)


最後的結果就是:

ending....num = 199

結果並不是我們想要的。來分析下為何會出現這種現象。

200個線程現在想統一修改一個全局變量,由於python解釋器的GIL鎖的限制,每次只能有一個線程在cpu上運行,在執行到sleep時,就相當於一次I/O操作,這時就會切到其他的線程,在執行sleep之前,當前運行的這個線程,這個線程取到的全局變量的值是200(temp_num = 200),還沒來得及做修改,就被切換到其他線程了,其他的線程也是一樣的道理,取到temp_num = 200這個值後,還沒來得及計算,執行到sleep觸發一次IO操作後,又切到了其他的線程,第2個第3個直到最後一個線程都拿到了temp_num=200這個變量後,後面的計算操作才會開始運行!(不要忘記一個概念,線程在切換之前,是會保存當前執行狀態的)當所有線程都拿到了emp_num=200這個變量後,每個線程都會自己執行一遍

num = temp_num - 1這也就導致了每個線程執行的都是200-1 所以,最後的結果就等於199.


例子2.還拿剛剛寫的那個減法程序舉例,我們把sleep的時間縮短為0.001秒看看會出現什麽效果?

還是上一段代碼,只不過把count_num函數的time.sleep(0.1)改為time.sleep(0.001)看看出現了什麽效果。


我們把執行過程也輸出一下:

i am Thread-1 , set num 200

i am Thread-2 , set num 200

i am Thread-3 , set num 200

i am Thread-4 , set num 200

i am Thread-5 , set num 200

i am Thread-6 , set num 200

i am Thread-7 , set num 200

i am Thread-8 , set num 200

i am Thread-9 , set num 200

i am Thread-10 , set num 200

i am Thread-11 , set num 199

i am Thread-12 , set num 199

i am Thread-13 , set num 198

i am Thread-14 , set num 198

i am Thread-15 , set num 197

i am Thread-16 , set num 197

i am Thread-17 , set num 197

i am Thread-18 , set num 197

i am Thread-19 , set num 196

i am Thread-20 , set num 196

i am Thread-21 , set num 196

i am Thread-22 , set num 195

i am Thread-23 , set num 195

i am Thread-24 , set num 194

i am Thread-25 , set num 194

i am Thread-26 , set num 194

i am Thread-27 , set num 193

i am Thread-28 , set num 193

i am Thread-29 , set num 192

i am Thread-30 , set num 192

i am Thread-31 , set num 192

i am Thread-32 , set num 191

i am Thread-33 , set num 190

i am Thread-34 , set num 189

i am Thread-35 , set num 189

i am Thread-36 , set num 188

i am Thread-37 , set num 187

i am Thread-38 , set num 186

i am Thread-39 , set num 186

i am Thread-40 , set num 185

i am Thread-41 , set num 185

i am Thread-42 , set num 184

i am Thread-43 , set num 184

i am Thread-44 , set num 184

i am Thread-45 , set num 184

i am Thread-46 , set num 184

i am Thread-47 , set num 183

i am Thread-48 , set num 182

i am Thread-49 , set num 182

i am Thread-50 , set num 181

i am Thread-51 , set num 179

i am Thread-52 , set num 179

i am Thread-53 , set num 179

i am Thread-54 , set num 179

i am Thread-55 , set num 177

i am Thread-56 , set num 177

i am Thread-57 , set num 177

i am Thread-58 , set num 177

i am Thread-59 , set num 177

i am Thread-60 , set num 176

i am Thread-61 , set num 175

i am Thread-62 , set num 175

i am Thread-63 , set num 174

i am Thread-64 , set num 174

i am Thread-65 , set num 174

i am Thread-66 , set num 174

i am Thread-67 , set num 173

i am Thread-68 , set num 171

i am Thread-69 , set num 171

i am Thread-70 , set num 171

i am Thread-71 , set num 170

i am Thread-72 , set num 169

i am Thread-73 , set num 168

i am Thread-74 , set num 167

i am Thread-75 , set num 166

i am Thread-76 , set num 166

i am Thread-77 , set num 165

i am Thread-78 , set num 165

i am Thread-79 , set num 165

i am Thread-80 , set num 165

i am Thread-81 , set num 164

i am Thread-82 , set num 164

i am Thread-83 , set num 163

i am Thread-84 , set num 162

i am Thread-85 , set num 162

i am Thread-86 , set num 162

i am Thread-87 , set num 160

i am Thread-88 , set num 159

i am Thread-89 , set num 159

i am Thread-90 , set num 158

i am Thread-91 , set num 157

i am Thread-92 , set num 156

i am Thread-93 , set num 156

i am Thread-94 , set num 156

i am Thread-95 , set num 156

i am Thread-96 , set num 156

i am Thread-97 , set num 155

i am Thread-98 , set num 154

i am Thread-99 , set num 154

i am Thread-100 , set num 154

i am Thread-101 , set num 153

i am Thread-102 , set num 152

i am Thread-103 , set num 152

i am Thread-104 , set num 151

i am Thread-105 , set num 151

i am Thread-106 , set num 151

i am Thread-107 , set num 151

i am Thread-108 , set num 150

i am Thread-109 , set num 149

i am Thread-110 , set num 149

i am Thread-111 , set num 149

i am Thread-112 , set num 149

i am Thread-113 , set num 149

i am Thread-114 , set num 149

i am Thread-115 , set num 149

i am Thread-116 , set num 149

i am Thread-117 , set num 149

i am Thread-118 , set num 149

i am Thread-119 , set num 149

i am Thread-120 , set num 149

i am Thread-121 , set num 149

i am Thread-122 , set num 149

i am Thread-123 , set num 148

i am Thread-124 , set num 147

i am Thread-125 , set num 147

i am Thread-126 , set num 145

i am Thread-127 , set num 145

i am Thread-128 , set num 145

i am Thread-129 , set num 144

i am Thread-130 , set num 143

i am Thread-131 , set num 142

i am Thread-132 , set num 142

i am Thread-133 , set num 142

i am Thread-134 , set num 142

i am Thread-135 , set num 142

i am Thread-136 , set num 142

i am Thread-137 , set num 141

i am Thread-138 , set num 141

i am Thread-139 , set num 141

i am Thread-140 , set num 140

i am Thread-141 , set num 140

i am Thread-142 , set num 139

i am Thread-143 , set num 139

i am Thread-144 , set num 139

i am Thread-145 , set num 139

i am Thread-146 , set num 138

i am Thread-147 , set num 138

i am Thread-148 , set num 137

i am Thread-149 , set num 136

i am Thread-150 , set num 136

i am Thread-151 , set num 136

i am Thread-152 , set num 136

i am Thread-153 , set num 136

i am Thread-154 , set num 135

i am Thread-155 , set num 135

i am Thread-156 , set num 135

i am Thread-157 , set num 134

i am Thread-158 , set num 133

i am Thread-159 , set num 133

i am Thread-160 , set num 133

i am Thread-161 , set num 133

i am Thread-162 , set num 132

i am Thread-163 , set num 131

i am Thread-164 , set num 131

i am Thread-165 , set num 131

i am Thread-166 , set num 131

i am Thread-167 , set num 131

i am Thread-168 , set num 130

i am Thread-169 , set num 130

i am Thread-170 , set num 130

i am Thread-171 , set num 130

i am Thread-172 , set num 129

i am Thread-173 , set num 127

i am Thread-174 , set num 127

i am Thread-175 , set num 127

i am Thread-176 , set num 127

i am Thread-177 , set num 127

i am Thread-178 , set num 126

i am Thread-179 , set num 126

i am Thread-180 , set num 125

i am Thread-181 , set num 124

i am Thread-182 , set num 124

i am Thread-183 , set num 124

i am Thread-184 , set num 124

i am Thread-185 , set num 123

i am Thread-186 , set num 122

i am Thread-187 , set num 122

i am Thread-188 , set num 122

i am Thread-189 , set num 122

i am Thread-190 , set num 122

i am Thread-191 , set num 121

i am Thread-192 , set num 120

i am Thread-193 , set num 119

i am Thread-194 , set num 118

i am Thread-195 , set num 118

i am Thread-196 , set num 118

i am Thread-197 , set num 118

i am Thread-198 , set num 118

i am Thread-199 , set num 117

i am Thread-200 , set num 116

ending....num = 115

這個結果完全出乎意料,最終num變成了115。

接著來分析下造成這種結果的原因。

當sleep時間較短的時候,在線程切換的過程中,之前運行的線程的sleep就已經執行結束了,就會重新參與競爭cpu資源,在切得過程中,之前的線程sleep結束,就有了被切回去的可能,繼續執行後面的num = temp_num - 1 所以就會導致這種情況。


註意!!這裏面的sleep是用來模擬程序中的I/O操作!


從第二個例子中,我們可以看到一個全局資源被搶占的現象,沒有控制多個線程對一個全局資源的訪問控制,造成全局資源的損壞(這裏的損壞是指得到了我們不想要的結果)使我們無法預測程序最後執行的結果,如果想避免這種問題,就需要用到“互斥鎖”。

“互斥鎖”最主要的作用就是,保證在操作共享數據時,共享數據的完整性。

互斥鎖實現的方式,就是為每個共享的資源創建一個Lock對象,當需要訪問這個共享資源的時候,調用

這個鎖的acquire方法來獲取鎖的對象,資源訪問結束後,在調用release方法去解鎖。

我們對上面的程序進行整改,為此我們需要添加一個互斥鎖變量t_lock = threading.Lock(),然後在爭奪資源的時候之前我們會先搶占這把鎖t_lock.acquire(),對資源使用完成之後我們在釋放這把鎖t_lock.release().

#!/usr/local/bin/python2.7

# -*- coding:utf-8 -*-

import threading

import time

num = 1000

tread_list = []

t_lock = threading.RLock() #創建一個鎖的對象。

def add_num():

global num,temp_num

if t_lock.acquire(): #加鎖

temp_num = num

time.sleep(0.001) #執行sleep,相當於執行IO操作.

num = temp_num - 1

t_lock.release() #公共資源訪問和操作結束後,解鎖。

for i in range(200):

t = threading.Thread(target=add_num)

t.start()

tread_list.append(t)

for t in tread_list:

t.join()

print "ending....num = %s" %(num)


最後看下輸出結果:

ending....num = 0

之前的資源搶占現象得到了解決。


當一個線程去調用一個Lock對象的acquire()方法去得到一個鎖時,這把鎖就會進入一個“locked”鎖定的狀態,在鎖定時,每次只有一個線程可以獲得鎖,如果有第二個線程試圖去獲得鎖(去訪問操作共享資源時),去操作共享的數據時,第二個線程就會進入阻塞狀態,直到線程1對共享數據資源操作結束後,調用了這個lock對象的release方法後(此時的鎖已經變為“unlocked”狀態),線程二才可以去操作共享資源。


大概的加鎖思路就是這樣的:

import threading

R=threading.Lock() #創建一個鎖的對象

R.acquire() #加鎖

‘‘‘

對公共數據的操作 #執行了對公共數據的操作後

‘‘‘

R.release() #解鎖


最後補充~

寫到這裏,可能會有人覺得,互斥鎖和join沒什麽區別!!事實並非如此!

互斥鎖可以做到,只有對公共數據進行訪問或者操作的時候是串行模式!

如果使用了join,那麽兩個線程中所有執行的操作都會變為串行模式!!

這兩個還是有很大區別的!



本文出自 “reBiRTH” 博客,請務必保留此出處http://suhaozhi.blog.51cto.com/7272298/1924938

11.python並發入門(part3 多線程與互斥鎖)