Day13 进程和线程
参考资料:
1.github-100-days-进程和线程
2.廖雪峰-进程和线程
3.Daemon is not daemon, but what is it?
推荐工具:科赛
概念
- 进程:程序,fork、spawn,IPC(管道、信号、套接字、共享内存区等)
- 线程:CPU调度执行单元(1-n:进程-线程)
多进程
单进程与多进程对比
# demo1.0
# 普通
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入门到住院')
download_task('Peking Hot.avi')
end = time()
print('总耗时%.2f' % (end-start))
main()
开始下载。。。python入门到住院
python入门到住院下载完成!耗时5秒
开始下载。。。Peking Hot.avi
Peking Hot.avi下载完成!耗时5秒
总耗时10.01
# demo1.1
# 多进程
from multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleep
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))
main()
启动下载进程,进程号[129]
开始下载Python入门到住院.pdf…
启动下载进程,进程号[132]
开始下载Peking Hot.avi…
Peking Hot.avi下载完成,耗时6秒
Python入门到住院.pdf下载完成,耗时7秒
共耗时7.09秒
例子
我们启动两个进程,一个输出Ping,一个输出Pong,两个进程输出的Ping和Pong加起来一共10个。听起来很简单吧,但是如果这样写可是错的哦。
from multiprocessing import Process
from time import sleep
counter = 0
def sub_task(string):
global counter
while counter < 10:
print(('[%d]'+string) % counter, end='\t', flush=True) # flush=True,将信息立刻打印(https://blog.csdn.net/u013985241/article/details/86653356)
counter += 1
sleep(0.01)
def main():
Process(target=sub_task, args= ('Ping', )).start()
Process(target=sub_task, args= ('Pong', )).start()
main()
[0]Ping [0]Pong [1]Ping [1]Pong [2]Ping [2]Pong [3]Ping [3]Pong [4]Ping [4]Pong [5]Ping [5]Pong [6]Ping [6]Pong [7]Ping [7]Pong [8]Ping [8]Pong [9]Ping [9]Pong
多线程
例1
# demo1.0
# 单线程
from random import randint
from threading import Thread
from time import time, sleep
def download(filename):
print('开始下载%s...' % filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成,耗时%.2f秒' % (filename, time_to_download))
def main():
start = time()
t1 = Thread(target=download, args=('Pyton从入门到住院', ))
t1.start()
t2 = Thread(target=download, args=('Pyton从入门到精通', ))
t2.start()
t1.join() # 阻塞,主等子。超timeout,主关子(https://blog.csdn.net/zhiyuan_2007/article/details/48807761)
t2.join()
end = time()
print('耗时:%.2f' % (end-start))
main()
开始下载Pyton从入门到住院…
开始下载Pyton从入门到精通…
Pyton从入门到住院下载完成,耗时9.00秒Pyton从入门到精通下载完成,耗时9.00秒
耗时:9.01
例2
# 自定义线程类
from random import randint
from threading import Thread
from time import time, sleep
class DownloadTask(Thread):
def __init__(self, filename):
super().__init__()
self._filename = filename
def run(self):
print('开始下载%s...' % self._filename)
time_to_download = randint(5, 10)
sleep(time_to_download)
print('%s下载完成,耗时%.2f秒' % (self._filename, time_to_download))
def main():
start = time()
t1 = DownloadTask('Python1')
t1.start()
t2 = DownloadTask('Python2')
t2.start()
t1.join()
t2.join()
end = time()
print('耗时%.2f' % (end-start))
main()
开始下载Python1…
开始下载Python2…
Python2下载完成,耗时7.00秒
Python1下载完成,耗时8.00秒
耗时8.01
例3
# 用Lock处理临界资源问题
# GIL(Global Interpreter Lock)(全局解释器锁)
# 每100条字节码(http://python.jobbole.com/56761/),自动释放GIL锁
from time import sleep
from threading import Thread, Lock
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 # 计算存款后的余额
sleep(0.01) # 模拟受理存款业务需要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 money():
account = Account()
threads = []
for _ in range(100): # 创建100个存款的线程向同一个账户存钱
t = AddMoneyThread(account, 1)
threads.append(t)
t.start()
# 等所有存款线程执行完毕
for t in threads:
t.join()
print('余额:¥%d元' % account.balance)
money()
余额:¥100元
多进/线程的选择
多 | 稳定 | 资源 | 速度 | 例子 |
---|---|---|---|---|
进程 | 较好 | 高 | 较慢 | 早期Apache服务器(现在是混合[进+线]) |
线程 | 较差 | 较快 | 早期IIS服务器(现在是混合) |
影响因素
1.切换
- 多任务切换
- 环境保存、恢复
2.任务类型
类型 | 占资源 | 速度 | 特点 | 适用 | 语言 | 例子 |
---|---|---|---|---|---|---|
计算密集 | 多 | 效率低 | 多次任务切换耗时 | 多进程 | C | 视频编解码、格式转换等 |
I/O密集 | 少 | 慢于内存、CPU | 多线程 | Python(脚本) | 网络、存储介质I/O |
单线程+异步I/O
协程:单线程+异步I/O
优点:充分利用多核CPU,效率高
例子:Nginx服务器、Node.js开发的服务器程序等
max:多进程+协程
梗
- Python从入门到住院/放弃/颈椎病
报错
1.类型错误 :在字符串格式下并没有转换所有参数
TypeError: not all arguments converted during string formatting
# 错误
print('开始下载。。。' % filename)
# 正确
print('开始下载。。。%s' % filename)
2.类型错误:download_task()给予了一位参数位置,但是赋值14位
TypeError: download_task() takes 1 positional argument but 14 were given
# 错误
p2 = Process(target=download_task, args=('Peking Hot.avi'))
# 正确
# 元组只有一个参数时,需要在末尾加','
p2 = Process(target=download_task, args=('Peking Hot.avi', ))
3.类型错误:描述符“init”的super对象需要一个参数
TypeError: descriptor ‘init’ of ‘super’ object needs an argument
# 错误
class AddMoneyThread(Thread):
def __init__(self, account, money):
super.__init__()
self._account = account
self._money = money
# 正确
class AddMoneyThread(Thread):
def __init__(self, account, money):
super().__init__()
self._account = account
self._money = money
英语积累
1.Because I think the point of this assignment is you’re not supposed to leave threads unlocked.
因为我认为这个任务的重点是你不应该解锁线程。