进程和线程
每一个应用程序就是一个进程,操作系统以进程为单位分配存储空间和资源。程序是一堆指令的集合,操作系统将程序调入内存,为其分配资源,就是进程。多进程就是系统允许将多个程序同时加载到内存,在系统调度下并发的执行。
为什么还需要线程:进程只能在一个时间执行一件事。比如打开qq这个进程,它即需要监听你键盘的输入,又要监听其他人发给你的消息等,比如Word,它可以同时进行打字、拼写检查、打印等事情。不止一个线程(子任务)
每个进程都有独立的代码和存储空间,进程之间共享内存,硬盘,打印机等资源。
线程共享代码和存储空间,线程所使用的资源来自于所属进程的资源。
在操作系统中能同时运行多个进程(程序):而在同一个进程中有多个线程同时执行。
每一个进程启动时,都会最先创建一个线程,叫主线程,主线程再创建其他线程,一个进程有且只有一个主线程。
其他语言cpu是多核时,支持多线程同时进行,但python,无论cpu是单核还是多核,同时只能由一个线程在执行,根源是 GIL (全局解释器)的存在。某个线程要执行,就必须获得GIL,在一个进程中,只有一个GIL拿不到通行证的线程,就不允许进入 CPU 执行。
进程
进程可以通过Process创建,也可以通过Pool创建进程池批量创建进程。不同进程分配不同的内存,进程间通信通过队列(Queue)和管道(Pipe)等.
from multiprocessing import Process,Pool,Queue
from threading import Thread
from random import randint
import time,os
def downLoadTask(filename):
print('启动下载进程,进程号[%d]' % os.getpid())
timeDownLoad = randint(5,10)
print('开始下载%s' % filename)
time.sleep(timeDownLoad)
print('%s下载完成,耗时%d!!!' % (filename,timeDownLoad))
def main():
startTime = time.time()
p1 = Process(target=downLoadTask,args=('从入门到放弃',))
p1.start()
p2 = Process(target=downLoadTask,args=('从弃到成功',))
p2.start()
p1.join()
p2.join()
endTime = time.time()
print('共耗时%d' % (endTime-startTime))
if __name__ == '__main__':
main()
用Pool批量创建进程
from multiprocessing import Process,Pool,Queue
from threading import Thread
from random import randint
import time,os
def downLoadTask(filename):
print('启动下载进程,进程号[%d]' % os.getpid())
timeDownLoad = randint(5,10)
print('开始下载%s' % filename)
time.sleep(timeDownLoad)
print('%s下载完成,耗时%d!!!' % (filename,timeDownLoad))
def main2():
startTime = time.time()
poolList = Pool(3)
fileNameList = ['从入门到放弃','从放弃到成功','从成功到巅峰']
for name in fileNameList:
poolList.apply_async(downLoadTask,args=(name,))
poolList.close()
poolList.join()
endTime = time.time()
print('共耗时%d' % (endTime - startTime))
if __name__ == '__main__':
main2()
进程间通过Quene通信
from multiprocessing import Process,Pool,Queue
from threading import Thread
from random import randint
import time,os
def writeString(q,stringLst):
for one in stringLst:
print('Put %s to queue...' % one)
q.put(one)#将数据装进quene的一端
time.sleep(randint(1,2))
q.put(None) #进程的最后加入一个结束信号
def readString(q):
while True: #一直监听是否还有数据
s = q.get(True)#从quene的另一端获取数据
if s == None:
break
else:
print('Get %s from queue.' % s)
def main():
if __name__ == '__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=writeString, args=(q,['Lining','Anta','361']))
# 启动子进程pw,写入:
pw.start()
pr = Process(target=readString, args=(q,))
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
#等待pr结束
pr.join()
if __name__ == '__main__':
main()
多线程
多线程可以共用进程的内存资源,因此,线程的通信很简单,最直接的办法是创建一个全局变量,线程共享这个全局变量即可。但当多个线程共享一个变量(资源)的时候,很可能产生不可控的结果。如果一个资源被多个线程竞争使用,这个资源需要加上防护,即锁,否则多个线程同时操作这个资源,会使资源混乱。
这个例子展示中,账户金额就是资源。
import time
from threading import Thread,Lock
class Account(object):
def __init__(self):
self._balance = 0
@property
def balance(self):
return self._balance
def deposit(self,money):
#改变账户余额,首先获取锁
try:
newBalance = self._balance + money
time.sleep(0.1)
self._balance = newBalance
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()
threadLst = []
for _ in range(100):
t = AddMoneyThread(account,1)
t.start()
threadLst.append(t)
for i in threadLst:
i.join()
print('账户余额为: ¥%d元' % account.balance)
if __name__ == "__main__":
main()
100个线程分别向账户中转入1元钱,可以看到账户金额远远小于100 。多个线程同时向账户中存钱,执行newBalance = self._balance + money时, self._balance 的初始状态都是0.所以结果错误。因此,需要锁,Lock()对资源加以保护,只有获得锁的线程A才能操作资源lock.acquire(),其他线程阻塞起来,等线程A操作完毕后释放锁lock.release(),其他线程才有机会获取锁操作资源。
import time
from threading import Thread,Lock
class Account(object):
def __init__(self):
self._balance = 0
self._lock = Lock()
@property
def balance(self):
return self._balance
def deposit(self,money):
#改变账户余额,首先获取锁
self._lock.acquire()
try:
newBalance = self._balance + money
time.sleep(0.1)
self._balance = newBalance
finally:
# 在finally中执行释放锁的操作保证正常异常锁都能释放
self._lock.release()
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()
threadLst = []
for _ in range(100):
t = AddMoneyThread(account,1)
t.start()
threadLst.append(t)
for i in threadLst:
i.join()
print('账户余额为: ¥%d元' % account.balance)
if __name__ == "__main__":
main()
无论是多线程还是多进程,只要数量一多,效率肯定上不去,因为操作系统在进行进程或线程切换时,需要先保存当前执行的现场环境(内存,寄存器等),然后把新任务的执行环境准备好,这个切换虽然很快,但也是需要时间的。如果将任务分成计算密集型和I/O密集型。计算密集型需要大量计算,消耗CPU资源,效率全靠CPU能力,任务越多,花在切换上时间越多,效率越低。I/O密集型,任务特点,CPU消耗很少,大多时间在等待I/O操作完成,因此对于I/O密集型任务,可以启用多任务,减少I/O操作等待,而高效利用CPU。
现代操作系统对I/O的改进就是支持异步I/O,如果可以利用异步I/O,就可以用单线程实现多任务,Nginx就是支持异步I/O的Web服务器,它在单核CPU上利用单进程支持多任务,在多核CPU上可以运行多任务。
在python中,单线程+异步就是协程,单线程实现多任务。优势:1高效。子任务切换不是子线程切换,是由程序本身切换,没有开销。2不需要锁机制。只有一个线程,不存在同时写变量冲突,操作是只需要判断状态。
链接: [https://github.com/jackfrued/Python-100-Days/blob/master/Day01-15/13.%E8%BF%9B%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B.md]