多线程和多进程
进程
- 进程的概念
进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。进程可以通过fork或spawn的方式来创建新的进程来执行其他的任务,不过新的进程也有自己独立的内存空间,因此必须通过进程间通信机制(IPC,Inter-Process Communication)来实现数据共享,具体的方式包括管道、信号、套接字、共享内存区等。
python中的多进程
Unix和Linux操作系统上提供了fork()系统调用来创建进程,调用fork()函数的是父进程,创建出的是子进程,子进程是父进程的一个拷贝,但是子进程拥有自己的PID。fork()函数非常特殊它会返回两次,父进程中可以通过fork()函数的返回值得到子进程的PID,而子进程中的返回值永远都是0。Python的os模块提供了fork()函数。由于Windows系统没有fork()调用,因此要实现跨平台的多进程编程,可以使用multiprocessing模块的Process类来创建子进程,而且该模块还提供了更高级的封装,例如批量启动进程的进程池(Pool)、用于进程间通信的队列(Queue)和管道(Pipe)等
- 不使用多进程下载文件
from random import randint
from time import time, sleep
def download_task(filename):
print("开始下载%s..." % filename)
time_to_download = randint(5,10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
def main():
# 开始时间
start = time()
# 下载文件
download_task('python从入门到放弃.pdf')
download_task('Peking Hot.avi')
# 结束时间
end = time()
print("总共耗费了%.2f秒." % (end - start))
if __name__ == '__main__':
main()
运行结果:
开始下载python从入门到放弃.pdf...
python从入门到放弃.pdf下载完成! 耗费了10秒
开始下载Peking Hot.avi...
Peking Hot.avi下载完成! 耗费了5秒
总共耗费了15.00秒.
从上面的例子可以看出, 程序中的代码只能按顺序一点点的往下执行, 一个文件下载完成后才能开始下一个任务.
- 使用多进程
from random import randint
from time import time, sleep
from multiprocessing import Process
from os import getpid
def download_task(filename):
print('启动下载进程, 进程号[%d]' % getpid())
print("开始下载%s..." % filename)
time_to_download = randint(5,10)
sleep(time_to_download)
print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
def main():
# 开始时间
start = time()
# 创建多进程
p1 = Process(target=download_task, args=('python从入门到放弃.pdf',))
p1.start()
p2 = Process(target=download_task, args=('Peking Hot.avi',))
p2.start()
p1.join()
p2.join()
# 结束时间
end = time()
print("总共耗费了%.2f秒." % (end - start))
if __name__ == '__main__':
main()
在上面的代码中, 我们通过Process类创建了进程对象, 通过target参数我们传入一个函数来表示进程启动后要执行的代码,
后面的args是一个元组, 它代表了传递给函数的参数, Process对象的start方法用来启动进程,
而join方法表示等待进程执行结束.
python 中的多线程
目前的多线程开发推荐使用threading模块,该模块对多线程编程提供了更好的面向对象的封装
- 使用多线程下载文件
from time import time,sleep
from threading import Thread
from random import randint
def download(filename):
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成, 耗费了%d秒' % (filename, time_to_download))
def main():
start = time()
t1 = Thread(target=download, args=('Python从入门到放弃',))
t1.start()
t2 = Thread(target=download, args=('Peking hot.avi',))
t2.start()
t1.join()
t2.join()
end = time()
print('总共耗费了%.3f秒' % (end - start))
if __name__ == '__main__':
main()
多个线程共享同一个变量的时候, 很有可能产生不可控的结果从而导致程序失效甚至崩溃.如果一个资源被多个线程竞争使用, 我们需要对 临界资源 的访问加上保护.
- 100个线程向同一个银行账户转账的场景
from threading import Thread, Lock
from time import sleep
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
def deposit(self, money):
# 先获取锁才能执行后续的代码
self._lock.acquire()
try:
# 计算存款后的余额
new_balance = self._balance + money
# 模拟受理存款业务需要0.01秒时间
sleep(0.01)
# 修改账户余额
self._balance = new_balance
finally:
self._lock.release()
@property
def balance(self):
return self._balance
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
def run(self):
self._account.deposit(self._money)
def main():
# 计算余额
account = Account()
threads = []
# 创建100个存款的线程向同一个账户中存钱
for _ in range(100):
# 添加金额
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款的线程都执行完毕
for t in threads:
t.join()
print('账户余额为: %d元' % account.balance)
if __name__ == '__main__':
main()