Justin-刘清政的博客

1-Python中的GIL

2020-03-25

一、同步锁

1.1 多个线程抢占资源的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from threading import Thread
import os,time
def work():
global n
temp=n
time.sleep(0.1)
n=temp-1
if __name__ == '__main__':
n=100
l=[]
for i in range(100):
p=Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()

print(n) #结果可能为99

1.1.1 对公共数据的操作

1
2
3
4
5
6
7
import threading
R=threading.Lock()
R.acquire()
'''
对公共数据的操作
'''
R.release()

1.2 同步锁的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from threading import Thread,Lock
import os,time
def work():
global n
lock.acquire()
temp=n
time.sleep(0.1)
n=temp-1
lock.release()
if __name__ == '__main__':
lock=Lock()
n=100
l=[]
for i in range(100):
p=Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()

print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

1.3 互斥锁与join的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
global n
print('%s is running' %current_thread().getName())
temp=n
time.sleep(0.5)
n=temp-1


if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()

stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''


#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread,Thread,Lock
import os,time
def task():
#未加锁的代码并发运行
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
#加锁的代码串行运行
lock.acquire()
temp=n
time.sleep(0.5)
n=temp-1
lock.release()

if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''

# 有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊

# 没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是

# start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的

# 单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
temp=n
time.sleep(0.5)
n=temp-1


if __name__ == '__main__':
n=100
lock=Lock()
start_time=time.time()
for i in range(100):
t=Thread(target=task)
t.start()
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''


二、死锁与递归锁

进程也有死锁与递归锁,在进程那里忘记说了,放到这里一起说了。

所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁

2.1 死锁

1
2
3
4
5
6
7
8
from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

解决方法:递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

2.2 递归锁RLock

1
2
3
4
5
6
7
8
from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

三、典型问题:科学家吃面

3.1 死锁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
noodle_lock.acquire()
print('%s 抢到了面条'%name)
fork_lock.acquire()
print('%s 抢到了叉子'%name)
print('%s 吃面'%name)
fork_lock.release()
noodle_lock.release()

def eat2(name):
fork_lock.acquire()
print('%s 抢到了叉子' % name)
time.sleep(1)
noodle_lock.acquire()
print('%s 抢到了面条' % name)
print('%s 吃面' % name)
noodle_lock.release()
fork_lock.release()

for name in ['哪吒','lqz','tank']:
t1 = Thread(target=eat1,args=(name,))
t2 = Thread(target=eat2,args=(name,))
t1.start()
t2.start()

3.2 递归锁解决死锁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import time
from threading import Thread,RLock
fork_lock = noodle_lock = RLock()
def eat1(name):
noodle_lock.acquire()
print('%s 抢到了面条'%name)
fork_lock.acquire()
print('%s 抢到了叉子'%name)
print('%s 吃面'%name)
fork_lock.release()
noodle_lock.release()

def eat2(name):
fork_lock.acquire()
print('%s 抢到了叉子' % name)
time.sleep(1)
noodle_lock.acquire()
print('%s 抢到了面条' % name)
print('%s 吃面' % name)
noodle_lock.release()
fork_lock.release()

for name in ['哪吒','lqz','egon']:
t1 = Thread(target=eat1,args=(name,))
t2 = Thread(target=eat2,args=(name,))
t1.start()
t2.start()

四 信号量(Semaphore)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
from threading import Thread,Semaphore
import time
import random
sm=Semaphore(5)

def task(name):
sm.acquire()
print('%s正在蹲坑'%name)

time.sleep(random.randint(1,3))
sm.release()

if __name__ == '__main__':
for i in range(100):
t=Thread(target=task,args=(i,))
t.start()

五 Event事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
# 比如一个线程等待另一个线程执行结束再继续执行

from threading import Thread,Event
import time
event=Event()
def girl(name):
print('%s 还在谈着恋爱'%name)
time.sleep(3)
event.set()
print('%s 单身了'%name)


def boy(name):
print('%s 等着女神分手'%name)
event.wait()
print('女神分手了,开始追')

if __name__ == '__main__':
t=Thread(target=girl,args=('刘亦菲',))
t.start()
for i in range(5):
t=Thread(target=boy,args=('屌丝男%s号'%i,))
t.start()


# 两个线程,一个读文件上部分,另一个读下部分

# 验证GIL锁的存在方式一
# from threading import Thread
# def task():
# while True:
# pass
#
#
# if __name__ == '__main__':
#
# for i in range(6):
# t=Thread(target=task,)
# t.start()

# GIL与普通互斥锁的区别

# from threading import Thread,Lock
# import time
# mutex=Lock()
# money=100
# def task():
# global money
# mutex.acquire()
# temp=money
# time.sleep(0.1)
# money=temp-1
# mutex.release()
#
#
# if __name__ == '__main__':
# ll=[]
# for i in range(100):
# t=Thread(target=task)
# t.start()
# ll.append(t)
# # t.join()
# for i,t in enumerate(ll):
# t.join()
# print(money)


# io密集型和计算密集型
# 单核:单核不管是计算密集还是io密集都用线程
# 多核:计算密集型用多进程,io密集型用多线程
from multiprocessing import Process
from threading import Thread
# import time
# def task():
# res=0
# for i in range(10000000):
# res+=i
# print(res)
#
# if __name__ == '__main__':
# ctime=time.time()
# ll=[]
# for i in range(10):
# t=Thread(target=task)
# # t=Process(target=task)
# t.start()
# ll.append(t)
# for t in ll:
# t.join()
# print(time.time()-ctime)

#
# from threading import Thread
# import time
# def task():
# time.sleep(2)
#
# if __name__ == '__main__':
# ctime=time.time()
# ll=[]
# for i in range(10):
# t=Thread(target=task)
# # t=Process(target=task)
# t.start()
# ll.append(t)
# for t in ll:
# t.join()
# print(time.time()-ctime)


# 死锁现象(哲学家就餐问题)
#递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock
# 这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。
# 直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁
# from threading import Thread,RLock,Lock
# import time
#
# # mutex1=RLock()
# # mutex2=mutex1=RLock()
# mutex2=mutex1=Lock()
#
# def eat1(name):
# mutex1.acquire()
# print('%s抢到了第一只筷子'%name)
# mutex2.acquire()
# print('%s抢到了第二只筷子'%name)
# print('%s开始吃饭'%name)
# mutex2.release()
# print('%s放下第二只筷子'%name)
# mutex1.release()
# print('%s放下第一只筷子'%name)
#
# def eat2(name):
# mutex2.acquire()
# print('%s抢到了第二只筷子'%name)
# print('%s思考了1s'%name)
# time.sleep(1)
# mutex1.acquire()
# print('%s抢到了第一只筷子'%name)
# print('%s开始吃饭'%name)
# mutex2.release()
# print('%s放下第一只筷子'%name)
# mutex1.release()
# print('%s放下第二只筷子'%name)
#
# for name in ['z','s','b']:
# t1=Thread(target=eat1,args=(name,))
# t2=Thread(target=eat2,args=(name,))
# t1.start()
# t2.start()


# Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
# from threading import Thread,Semaphore
# import time
# import random
# sm=Semaphore(5)
#
# def task(name):
# sm.acquire()
# print('%s正在蹲坑'%name)
#
# time.sleep(random.randint(1,3))
# sm.release()

# if __name__ == '__main__':
# for i in range(100):
# t=Thread(target=task,args=(i,))
# t.start()




# Event事件
# from threading import Thread,Event
# import time
# event=Event()
# def girl(name):
# print('%s 还在谈着恋爱'%name)
# time.sleep(3)
# event.set()
# print('%s 单身了'%name)
#
#
# def boy(name):
# print('%s 等着女神分手'%name)
# event.wait()
# print('女神分手了,开始追')
#
# if __name__ == '__main__':
# t=Thread(target=girl,args=('刘亦菲',))
# t.start()
# for i in range(5):
# t=Thread(target=boy,args=('屌丝男%s号'%i,))
# t.start()




# 写两个线程,一个线程读文件前半部分,另一个线程读后半部分

'''
f.seek(指针移动的字节数,模式控制): 控制文件指针的移动
模式控制:
0: 默认的模式,该模式代表指针移动的字节数是以文件开头为参照的
1: 该模式代表指针移动的字节数是以当前所在的位置为参照的1
2: 该模式代表指针移动的字节数是以文件末尾的位置为参照的
强调:其中0模式可以在t或者b模式使用,而1跟2模式只能在b模式下用
'''
from threading import Thread,Event
import time
import os
event=Event()
size = os.path.getsize('a.txt')
def read_first():
with open('./a.txt','r',encoding='utf-8') as f:
n = size // 2
data=f.read(n)
print(data)
print('读完前面')
event.set()


def read_last():
event.wait()
with open('./a.txt', 'r', encoding='utf-8') as f:
n = size // 2
f.seek(n, 0) # 将游标设置到读文件的一半
data = f.read()
print(data)


if __name__ == '__main__':
t1=Thread(target=read_first)
t2=Thread(target=read_last)
t1.start()
t2.start()
Tags: Python
使用支付宝打赏
使用微信打赏

点击上方按钮,请我喝杯咖啡!

扫描二维码,分享此文章