# 进程和线程
进程(Process)是操作系统进行资源分配和调度的基本单位,是程序执行时的一个实例。它拥有自己独立的地址空间,包含程序计数器、寄存器、堆栈段、数据段等,以及一个或多个线程。
特点:
独立性:每个进程都拥有自己独立的地址空间和系统资源,如内存、文件句柄等。
并发性:多个进程可以在操作系统中并发执行,但进程间的通信和同步较为复杂。
动态性:进程是程序执行时的一个实例,具有生命周期,从创建、运行到消亡。
线程(Thread)是进程内的一个执行单元,也是操作系统调度的基本单位。它共享进程的资源(如地址空间、文件句柄等),但拥有自己的程序计数器、寄存器、堆栈等。
特点:
共享性:线程共享其所属进程的地址空间和资源,这使得线程间的通信和同步相对简单。
并发性:多个线程可以在一个进程内并发执行,提高程序的执行效率。
轻量级:线程的创建、销毁和切换相对于进程来说开销较小。
# 进程与线程的区别与联系
区别:
资源分配:进程是资源分配的基本单位,每个进程都有独立的地址空间和系统资源;而线程共享其所属进程的地址空间和资源。
独立性:进程具有独立性,进程间的通信和同步较为复杂;而线程间的通信和同步相对简单。
开销:进程的创建、销毁和切换开销较大;而线程的创建、销毁和切换开销较小。
联系:
包含关系:一个进程可以包含多个线程,这些线程共享进程的资源。
调度关系:进程是操作系统调度的基本单位,但线程也是操作系统调度的基本单位。操作系统通常以线程为单位进行调度和执行。
并发执行:进程和线程都可以实现并发执行,但线程间的并发执行通常更为高效。
# 多进程
举个例子,比如下载两个文件:
from random import randint
from time import time,sleep
def download_file(filename):
print(f'start download {filename} ...')
time_to_download = randint(5,10)
sleep(time_to_download)
print(f'{filename} download done, consume {time_to_download} seconds')
def main():
start_at = time()
download_file('file_a')
download_file('file_b')
end_at = time()
print(f'total need {end_at - start_at}')
if __name__ == '__main__':
main()
可以看出,代码是从上至下执行,并且需要等下载好一个文件,另外一个文件才会开始下载,这样做没有效率,下面使用多进程进行优化。
from multiprocessing import Process
from os import getpid
from random import randint
from time import time,sleep
def download_file(filename):
pid = getpid()
print(f'process id is {pid}')
print(f'start download {filename} ...')
time_to_download = randint(5,10)
sleep(time_to_download)
print(f'{filename} download done, consume {time_to_download} seconds')
def main():
start_at = time()
p1 = Process(target=download_file,args=('filename_a',))
p1.start()
p2 = Process(target=download_file,args=('filename_b',))
p2.start()
p1.join()
p2.join()
end_at = time()
print(f'total need {end_at - start_at}')
if __name__ == '__main__':
main()
猜猜下面的列子会输出什么?为什么?
from multiprocessing import Process
from time import time,sleep
counter = 0
def sub_task(string):
global counter
while counter < 10:
print(string,end='',flush=True)
counter += 1
sleep(0.01)
def main():
Process(target=sub_task,args=('Ping',)).start()
Process(target=sub_task,args=('Pong',)).start()
if __name__ == '__main__':
main()
最后的结果是Ping和Pong各输出了10个,当我们在程序中创建进程的时候,子进程复制了父进程及其所有的数据结构,每个子进程有自己独立的内存空间,这也就意味着两个子进程中各有一个counter变量,所以结果也就可想而知了,要想解决这个问题就需要进程间通信。
# 多线程
尝试使用多线程进行文件下载
from random import randint
from threading import Thread
from time import time,sleep
def download_file(filename):
print(f'start download {filename} ...')
time_to_download = randint(5,10)
sleep(time_to_download)
print(f'{filename} download done, consume {time_to_download} seconds')
def main():
start_at = time()
t1 = Thread(target=download_file,args=('filenameA',))
t1.start()
t2 = Thread(target=download_file,args=('filenameB',))
t2.start()
t1.join()
t2.join()
end_at = time()
print(f'total need {end_at - start_at}')
if __name__ == '__main__':
main()
有个有趣的列子,搞100个线程,分别往一个账户里充1块钱,看看最后账户余额是多少。
from time import sleep
from threading import Thread
class Account(object):
def __init__(self):
self._balance = 0
def deposit(self, money):
# 计算存款后的余额
new_balance = self._balance + money
# 模拟受理存款业务需要0.01秒的时间
sleep(0.01)
# 修改账户余额
self._balance = new_balance
@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()
运行了之后竟然不是100块,之所以出现这种情况是没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,多个线程得到的账户余额都是初始状态下的0,所以都是0上面做了+1的操作,因此得到了错误的结果。在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。
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()