一、锁的概念。
锁,通常被用来实现共享数据的访问,为每一个共享的数据,创建一个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,那么两个线程中所有执行的操作都会变为串行模式!!
这两个还是有很大区别的!