串行,并行与并发
串行:在一个CPU上,按顺序完成多个任务 并行:当任务数小于等于CPU核数时,这些任务是一起执行的 并发:一般任务数多于CPU核数,再一个CPU上,交替的处理多个任务,它是通过操作系统的各种任务调度算法,实现用多个任务"一起"执行
进程、线程和协程

进程
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。现代操作系统都是支持多任务的,简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用逛淘宝,一边在听音乐,一边在用微信聊天,这就是多任务,至少同时有3个3任务正在运行。对于操作系统来说,一个任务就是一个进程(Process)。
线程
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。有些进程还不止同时干一件事,比如微信,它可以同时进行打字聊天,视频聊天,朋友圈等事情。在一个进程内部,要同时干多件事,就需要同时运行多个"子任务",我们把进程内的这些 "子任务"称为线程(Thread)。
线程的创建
Python的标准库提供了两个模块: _ thread 和 threading , _ thread是低级模块, threading是高级模块,对 _ thread 进行了封装。线程可以通过两种方式创建:
- 方法包装
python
def func1(name):
for i in range(3):
print(f"thread:{name}:{i}")
sleep(1)
if __name__ == '__main__':
print("主线程start...")
t1 = Thread(target=func1,args=("t1",))
t2 = Thread(target=func1,args=("t2",))
t1.start()
t2.start()
print("主线程end...")
"""
主线程start...
thread:t1:0
thread:t2:0
主线程end...
thread:t2:1thread:t1:1
thread:t1:2
thread:t2:2
"""
- 类包装
python
class MyThread(Thread):
def __init__(self,name):
Thread.__init__(self)
self.name = name
def run(self):
for i in range(3):
print(f"thread:{self.name}:{i}")
sleep(1)
if __name__ =="__main__":
print("主线程start...")
t1 = MyThread("t1")
t2 = MyThread("t2")
t1.start()
t2.start()
print("主线程end...")
"""
主线程start...
thread:t1:0
thread:t2:0主线程end...
thread:t2:1
thread:t1:1
thread:t1:2thread:t2:2
"""
join()
从上面的结果来看,主线程不会等待子线程结束,而是执行完毕之后,主线程直接关闭,如果想要主线程等待某一个线程结束之后,主线程再结束,就可以使用join方法
python
class MyThread(Thread):
def __init__(self, name):
Thread.__init__(self)
self.name = name
def run(self):
for i in range(3):
print(f"thread:{self.name}:{i}")
sleep(1)
if __name__ == "__main__":
print("主线程start...")
t1 = MyThread("t1")
t2 = MyThread("t2")
t1.start()
t2.start()
t1.join()
# t2.join()
print("主线程end...")
"""
主线程start...
thread:t1:0
thread:t2:0
thread:t2:1thread:t1:1
thread:t1:2thread:t2:2
主线程end...
"""
守护线程
当主线程执行结束后,其守护线程不管有没有执行结束,都会立刻结束
python
class MyThread(Thread):
def __init__(self, name):
Thread.__init__(self)
self.name = name
def run(self):
for i in range(3):
print(f"thread:{self.name}:{i}")
sleep(1)
if __name__ == "__main__":
print("主线程start...")
t1 = MyThread("t1")
# t1设置为守护线程
t1.daemon = True
t1.start()
print("主线程end...")
"""
主线程start...
thread:t1:0
主线程end...
Process finished with exit code 0
"""
线程同步和互斥锁
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候,我们就需要用到"线程同步"。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
未使用线程同步的情况:
python
class Account:
def __init__(self, money, name):
self.money = money
self.name = name
class Drawing(Thread):
def __init__(self,drawingNum,account):
Thread.__init__(self)
self.drawingNum = drawingNum
self.account = account
self.expenseTotal = 0
def run(self):
if self.account.money < self.drawingNum:
return
sleep(1)
self.account.money -= self.drawingNum
self.expenseTotal += self.drawingNum
print(f"账户:{self.account.name},余额是:{self.account.money}")
print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")
if __name__ == "__main__":
a1 = Account(100,"hhc")
draw1 = Drawing(80,a1)
draw2 = Drawing(80,a1)
draw1.start()
draw2.start()
"""
账户:hhc,余额是:20账户:hhc,余额是:-60
账户:hhc,总共取了:80
账户:hhc,总共取了:80
"""
没有线程同步机制,两个线程同时操作同一个账户对象,从只有100元的账户,轻松取出160元,账户余额竟然成为了-60
通过互斥锁实现线程同步:
互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。多个线程一起去抢,抢到锁的线程先执行,没有抢到 锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。 threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁
python
class Account:
def __init__(self, money, name):
self.money = money
self.name = name
class Drawing(Thread):
def __init__(self,drawingNum,account):
Thread.__init__(self)
self.drawingNum = drawingNum
self.account = account
self.expenseTotal = 0
def run(self):
lock1.acquire()
if self.account.money < self.drawingNum:
print("余额不足")
return
sleep(1)
self.account.money -= self.drawingNum
self.expenseTotal += self.drawingNum
lock1.release()
print(f"账户:{self.account.name},余额是:{self.account.money}")
print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")
if __name__ == "__main__":
a1 = Account(100,"hhc")
lock1 = Lock()
draw1 = Drawing(80,a1)
draw2 = Drawing(80,a1)
draw1.start()
draw2.start()
"""
账户:hhc,余额是:20
账户:hhc,总共取了:80
余额不足
"""
死锁
在多线程程序中,死锁问题很大一部分是由于一个线程同时获取多个锁造成的
python
def fun1():
lock1.acquire()
print("fun1拿到菜刀")
sleep(2)
lock2.acquire()
print("fun2拿到锅")
lock2.release()
print("fun2释放锅")
lock1.release()
print("fun2释放菜刀")
def fun2():
lock2.acquire()
print('fun2拿到锅')
lock1.acquire()
print('fun2拿到菜刀')
lock1.release()
print('fun2释放菜刀')
lock2.release()
print('fun2释放锅')
if __name__ == '__main__':
lock1 = Lock()
lock2 = Lock()
t1 = Thread(target=fun1)
t2 = Thread(target=fun2)
t1.start()
t2.start()

死锁是由于"同步块需要同时持有多个锁造成"的,要解决这个问题,思路很简单,就是同一个代码块,不要同时持有两个对象锁。
信号量
互斥锁使用后,一个资源同时只有一个线程访问。
信号量:一个资源同一时间可以多个线程进行访问。
信号量底层就是一个内置的计数器。每当资源获取时(调用acquire)计数器-1,资源释放时(调用release)计数器+1
python
from threading import Thread, Lock
from time import sleep
from multiprocessing import Semaphore
# 信号量
"""
一个房间一次只允许两个人通过
若不使用信号量,会造成所有人都进入这个房子
若只允许一人通过可以用锁-Lock()
"""
def home(name, se):
se.acquire()
print(f"{name}进入了房间")
sleep(3)
print((f'******************{name}走出来房间'))
se.release()
if __name__ == "__main__":
# 同时只允许两个线程进入
se = Semaphore(2)
for i in range(7):
p = Thread(target=home, args=(f"tom{i}", se))
p.start()
"""
tom0进入了房间
tom1进入了房间
******************tom0走出来房间
******************tom1走出来房间
tom3进入了房间tom2进入了房间
******************tom3走出来房间
******************tom2走出来房间
tom4进入了房间
tom5进入了房间
******************tom5走出来房间******************tom4走出来房间
tom6进入了房间
******************tom6走出来房间
Process finished with exit code 0
"""
协程
协程是一种在线程中,比线程 更加轻量级的存在,由程序员自己写程序来管理。当出现IO阻塞时,CPU一直等待IO返回,处于空转状态。这时候用 协程,可以执行其他任务。当IO返回结果后,再回来处理数据。充分利用了IO等待的时间,提高了效率。
同步和异步
同步和异步强调的是消息通信机制
同步:A调用B,等待B返回结果后,A继续执行
异步:A调用B,A继续执行,不等待B返回结果;B 有结果了,通知A,A再做处理