Python进程和线程
开始Python的复习!(以前学的忘了好多-_-)
主要参考的是Github上的一个项目:https://github.com/jackfrued/Python-100-Days
文章主要是对该项目中的内容进行学习 穿插一点自己的学习想法等内容~
概念解释
进程,操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。
一个进程还可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。
由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核CPU系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。
简单理解进程就是要执行的一个分任务,线程就是同时执行该分任务的人数。
Python既支持多进程又支持多线程,使用Python进行并发编程主要有三种方式:多线程、多进程、多线程+多进程
Python中的多进程
windows系统没有fork()调用,要实现跨平台的多进程编辑可以使用multiprocessing模块的process类来创建子进程而且该模块还提供了更高级的封装,例如批量启动进程的进程池(Pool
)、用于进程间通信的队列(Queue
)和管道(Pipe
)等。
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))
if __name__ == '__main__':
main()
在上边的代码中,通过process类创建了进程对象,通过target参数传入一个函数表示进程启动后要执行的代码,后边的args是一个元组,代表了传递给函数的参数。
process对象的start方法用来启动进程,join方法表示等待进程执行结束。上边的代码表示两个进程“同时”启动。
也可以使用subprocess模块中的类和函数来创建和启动子进程,然后通过管道来和子进程通信。
Python中的多线程
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下载完成! 耗费了%d秒' % (filename, time_to_download))
def main():
start = time()
t1 = Thread(target=download, args=('Python从入门到住院.pdf',))
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()
可以直接使用threading模块的Thread
类来创建线程,也可以从已有的类创建新类,通过继承Thread
类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。
特性的继承,从已有创造出更加方便的东西
因为多个线程可以共享进程的内存空间,要实现多个线程之间的通信,最直接的办法就是设置一个全局变量 参数共享。
但是当多个线程共享同一个变量(通常称之为“资源”)时,可能产生不可控的结果导致程序崩溃。
如果一个资源被多个线程竞争使用,那么通常称为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。
当所有进程同时启动时,他们使用的初始值是一样的,会导致崩溃。具体可以参见链接中的笔记13。
在上述提及到的资源访问问题中,可以通过“锁“来保护”临界资源“,只有获得锁的线程才能访问临界资源。(疑问:那这样的多线程和单线程有区别吗?不是依然是按顺序执行吗?)
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)
self._balance = new_balance
finally:
# 在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 = []
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()
创建锁对象,加锁,解除锁
知识科普:
比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题,但是即便如此,就如我们之前举的例子,使用多线程在提升执行效率和改善用户体验方面仍然是有积极意义的。
参考链接:
sleep()的理解:https://blog.csdn.net/qq_38777624/article/details/89185878
锁的理解:https://blog.csdn.net/weixin_42100915/article/details/80421525