线程
根据学进程的节奏,接下来该学锁了。线程也有锁,也分为互斥锁和 递归锁。线程锁较进程锁使用的更为广泛。
首先我先解释一下死锁
1.死锁:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
举个经典的例子看一下吧 科学家吃面的问题
互斥锁(一般锁)
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
fork_lock.acquire()
print('%s拿到叉子了'%name)
noodle_lock.acquire()
print('%s拿到面了'%name)
print('%s吃面'%name)
fork_lock.release()
noodle_lock.release()
def eat2(name):
noodle_lock.acquire()
print('%s拿到面了'%name)
time.sleep(1)
fork_lock.acquire()
print('%s拿到叉子了'%name)
print('%s吃面'%name)
noodle_lock.release()
fork_lock.release()
Thread(target=eat1,args=('ming',)).start()
Thread(target=eat2,args=('hong',)).start()
Thread(target=eat1,args=('lan',)).start()
Thread(target=eat2,args=('huang',)).start()
运行结果:
解释:
首先面条锁 和 叉子锁是公共的且只有一把 ming吃饭积极 拿完叉子 拿面一气呵成 开心的吃了一口面
而 hong 拿到面之后 有点懵 就在这时候 lan把叉子拿到了 并且 面和叉子只有一份 两人互不相让 导致谁也吃不了,导致程序处于挂起状态----也就是著名的死锁。
递归锁
noodle_lock = fork_lock = RLock()
def eat1(name):
fork_lock.acquire()
print('%s拿到叉子了'%name)
noodle_lock.acquire()
print('%s拿到面了'%name)
print('%s吃面'%name)
fork_lock.release()
noodle_lock.release()
def eat2(name):
noodle_lock.acquire()
print('%s拿到面了'%name)
time.sleep(1)
fork_lock.acquire()
print('%s拿到叉子了'%name)
print('%s吃面'%name)
noodle_lock.release()
fork_lock.release()
Thread(target=eat1,args=('ming',)).start()
Thread(target=eat2,args=('hong',)).start()
Thread(target=eat1,args=('lan',)).start()
Thread(target=eat2,args=('huang',)).start()
大家可能发现了,就只有定义锁的时候不一样。其次特别注意的是,定义锁的时候要像我这样两个叠在一起定义,如果像这样分别定义 noodle_lock = RLock() fork_lock = RLock() ,就定义成两个递归锁了
运行结果:
解释:
大家可以看到一个人拿到叉子必拿到面(或者拿到面必拿到叉子) 这时候的递归锁 相当于一个钥匙串 拿到了一把也就相当于拿到了整个钥匙串 在这里可以理解为 叉子和面条绑在了一起 拿到了其中一样东西 ,其他进程就争夺不到了
再给大家用一个图片深入一下递归锁的概念,因为进程中没出现过所以有些难懂。
2.信号量
目前对于信号量的理解 我是有一点疑义的。但我先保留我的意见,把信号量看成可以进行多个线程访问数据的功能。
举一个简单的例子吧:
# 要实现的功能:4个人可以同时进入房间取数据
sem = Semaphore(4)
def room(sem,a,b):
sem.acquire()
print(a+b)
time.sleep(2) # 这里的睡两秒是为了增强信号量的效果 想象他们执行完后 等待两秒才归还钥匙
sem.release()
for i in range(10):
Thread(target=room,args=(sem,i,i+1)).start()
运行结果:
解释:
结果是每4个每4个出现,其中有不按顺序出现,说明是异步。
3.事件
尽管写代码有些枯燥,但它能很好的证明我们想看到的结果。我就是从一个个代码的小例子理解每个概念的。
下面我们写一个关于事件的小例子,线程的事件和进程的事件是一样的,所以这算是个复习吧。希望大家能够和我一样,不要眼高手低,有时候代码敲10遍你会了,可是敲11遍的时候,你就有了新的理解。
# 用线程的事件来实现数据库连接的情况
e = Event()
def check(e):
time.sleep(random.randint(0,3))
e.set()
def connet(e):
count = 0
while count <3:
e.wait(0.5) # wait 里面放参数指的是 等待几秒
if e.is_set():
print('成功连接数据库')
break
else:
print('第%s次连接失败'%count)
count += 1
else:
print('数据库连接失败')
t1 = Thread(target=connet, args=(e,)).start()
t2 = Thread(target=check, args=(e,)).start()
解释:
e.wait(0.5) 先不去判断状态而是先等待0.5秒,过后再判断状态如果是True,则通信。 如果是False,则阻塞。