进程与线程
进程 计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到 内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中 的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。 线程 线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并 共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷 你进程”。 让步 线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下 文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫 做让步(yielding)。 单核和多核是如何实现并发的 一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线 程间的信息共享和通信更加容易。线程一般是以并发方式执行的,正是由于这种并行和数据 共享机制,使得多任务间的协作成为可能。当然,在单核 CPU 系统中,因为真正的并发是不 可能的,所以线程的执行实际上是这样规划的:每个线程运行一小会儿,然后让步给其他线 程(再次排队等待更多的 CPU 时间)。在整个进程的执行过程中,每个线程执行它自己特定 的任务,在必要时和其他线程进行结果通信。 当然,这种共享并不是没有风险的。如果两个或多个线程访问同一片数据,由于数据访 问顺序不同,可能导致结果不一致。这种情况通常称为竞态条件(race condition)。幸运的是, 大多数线程库都有一些同步原语,以允许线程管理器控制执行和访问。 守护线程: setDaemon() import threading import time def music(): print("start music %s" % time.ctime()) time.sleep(3) print("stop music %s" % time.ctime()) def game(): print("start game %s" % time.ctime()) time.sleep(5) print("stop game %s" % time.ctime()) # 13 if __name__ == "__main__": t1 = threading.Thread(target=music) t2 = threading.Thread(target=game) t1.start() t2.setDaemon(True) # 将t1设置为守护线程 t2.start() # t2.setDaemon(True) # 将t1设置为守护线程 """ 守护线程:没有守护线程时,主线程语句执行完后,等待子线程,子线程全部结束后,主线程结束, 有守护线程时,主线程语句执行完后,不等待守护线程,非守护线程结束后,主线程立即结束, 守护线程不论是否结束,都跟随主线程一起结束 """ print("ending.........") """ t2设置为守护线程后,则t1线程结束后,主线程结束,守护线程t2立即结束,13行未被执行 start music Tue Aug 27 08:55:21 2019 start game Tue Aug 27 08:55:21 2019 ending......... stop music Tue Aug 27 08:55:24 2019 """ join()方法,会等待线程调用了该方法的线程结束后,在向下继续执行 import threading import time def music(): print("start music %s" % time.ctime()) #6 time.sleep(3) print("stop music %s" % time.ctime()) #8 def game(): print("start game %s" % time.ctime()) #11 time.sleep(5) print("stop game %s" % time.ctime()) #13 if __name__ == "__main__": # t1 = threading.Thread(target=music) # 如果需要传参数 ,args=参数 # t2 = threading.Thread(target=game) # t1.start() # t2.start() # print("ending>>>>>>>") #20 # 6,11,20同时执行,3秒后8执行,再过2秒13执行 #jion()方法由实例调用,作用是等待实例执行完后继续执行后面的语句 t1 = threading.Thread(target=music) # 如果需要传参数 ,args=参数 t2 = threading.Thread(target=game) t1.start() t2.start() t2.join() """ join()方法,调用此方法的线程必须结束后才会继续向下执行, 也就是等待线程结束后才会继续执行 同时执行6,11,3秒后t1结束,5秒后t2结束,执行34 """ print("ending>>>>>>>")# 34 # 6,11同时执行,3秒后8执行,再过2秒13,34同时执行 实例的方法 join():等待线程 setDaemon():守护线程 run():线程被cpu调用后自动执行 start():启动线程活动,使处于等待被cpu调用的状态 isAlive():返回线程是否活动 getName():返回线程名,默认:thread-1 setName():设置线程名 threading提供的一些方法 #threading.currentThread():返回当前的线程变量 #threading.enumerate():返回正在运行的线程的列表 #threading.activeCount():返回正在运行的线程数量 GIL: python的GIL 全局解释器锁,这是在解释器中引入的,简单来讲,就是在一个进程中不允许CPU同时执行多个 线程,哪怕是多核CPU,也只能采用让步的方式轮循 线程执行的任务有两种:1 IO密集型 2 计算密集型 对于计算密集型,因为让步轮循的关系,反而不如串行执行的效率高 但是IO密集型不同,等待IO时其他线程继续执行,节省了等待IO的时间,此时多线程是有意义的 GIL的测试 import threading import time def add(): sum = 0 for i in range(10000000): sum += i print("sum",sum) def mul(): sum1 = 1 for i in range(1,100000): sum1 *= i print("sum1",sum1) if __name__ =="__main__": start = time.time() t1 = threading.Thread(target=add) t2 = threading.Thread(target=mul) l = [] l.append(t1) l.append(t2) # for t in l: # t.start() # # for t in l: # t.join() # cost time: 8.800450 add() mul() #cost time: 7.661081 print("cost time: %f" % (time.time()-start)) """ 会发现串行执行会比线程执行更快,这就是因为GIL的影响 """ 同步锁: 假设我们要做一个100的累减,通过使用100个线程来实现 import threading import time # def cut(): # global num # num-=1 # out: 0 # def cut(): # global num # temp = num # time.sleep(0.001) # num = temp-1 # time.sleep(0.01): out: 95 96 # time.sleep(0.001): out : 78 82 81 """ 会出现这种现象就是因为每个线程在自己的轮循时间内有阻塞,会立刻切换其他线程,但其他线程拿到的num 并没有被前一个线程改变,阻塞时间足够大的情况下,所有的线程拿到的num都是100,说以最后的结果num = 99 而线程在自己轮循时间内完成了对num的改变,之后的线程拿到的num就都是改变后的了 """ # num = 100 # l = [] # for i in range(100): # t = threading.Thread(target=cut) # t.start() # l.append(t) # # # for j in l: # j.join() # # print(num) # 0 # 若果想要在线程有阻塞或者,轮循时间内无法完成任务的情况下,实现目的,就要使用同步锁 #同步锁:同步锁之内的语句在未执行完的情况下不允许切换线程 def cut(): global num lock.acquire() #使用同步锁包围要同步的执行语句 这是类LockType() 中的方法 temp = num time.sleep(0.001) num = temp-1 lock.release() #使用同步锁包围要同步的执行语句 num = 100 l = [] lock = threading.Lock() # 创建一个实例LockType() for i in range(100): t = threading.Thread(target=cut) t.start() l.append(t) for j in l: j.join() print(num) # 0 同步对象 可以实现控制线程的执行顺序 event = threadin.Event event.wait() 未设置标志位时阻塞,标志位被设置时等同于pass event.set() 设置标志位 event.clear() 清除标志位 信号量 semaphore=threading.Semaphore(val) # val决定同时启动的线程数量 # 信号量就相当于停车位,停车位满了,后来的车就必须等待,直到车位被释放 举例: import threading import time class Mythread(threading.Thread): def run(self): if semaphore.acquire(): #semaphore.acquire() == True print(self.getName()) time.sleep(3) semaphore.release() if __name__ == "__main__": L = [] semaphore = threading.Semaphore(5) for i in range(100): L.append(Mythread()) for j in L: j.start() # 效果是每隔3秒执行五个线程 队列 import queue # 队列的三种模式 # 模式一 先进先出 q = queue.Queue() # ,还可以传入参数val=5,表示只允许put进5个元素 """ [1, 2, 3] 1 wa (1, 2) {'name': 'alxe'} """ q.put([1,2,3]) q.put(1) q.put("wa") q.put((1,2)) q.put({"name":"alxe"}) while True: date = q.get() print(date) #模式二 先进后出 q = queue.LifoQueue() """ {'name': 'alxe'} (1, 2) wa 1 [1, 2, 3] """ q.put([1,2,3]) q.put(1) q.put("wa") q.put((1,2)) q.put({"name":"alxe"}) while True: date = q.get() print(date) #模式三 按照优先级 q = queue.Queue() # 按照优先级 """ [1, 1] [2, (1, 2)] [3, 'wa'] [4, {'name': 'alxe'}] [5, [1, 2, 3]] """ q.put([5,[1,2,3]]) q.put([1,1]) q.put([3,"wa"]) q.put([2,(1,2)]) q.put([4,{"name":"alxe"}]) # block = False:当队列为满时,继续传入会报错 while True: date = q.get(block=False) # block = False :当队列为空时报错 """ raise Empty _queue.Empty # 报错,队列已满 """ print(date) q = queue.Queue(4) # 按照优先级 """ [1, 1] [2, (1, 2)] [3, 'wa'] [4, {'name': 'alxe'}] [5, [1, 2, 3]] """ q.put([5,[1,2,3]]) q.put([1,1]) q.put([3,"wa"]) q.put([2,(1,2)]) # 队列满了之后,在继续装入数据,会一直等待,直到队列中数据被取出,有空位为止 # q.put([4,{"name":"alxe"}]) # block = False:当队列为满时,继续传入会报错 """ raise Full queue.Full # 报错,队列已满 """ while True: # 取出全部数据后等待,直到再次有数据被装入 date = q.get() # block = False :当队列为空时报错 """ raise Empty _queue.Empty # 报错,队列已空 """ print(date) #其他方法 q = queue.Queue(2) q.put([5,[1,2,3]]) q.put([1,1]) # q.put_nowait() 相当于 q.put("wan",block=False) print(q.empty()) #队列是否为空 print(q.full()) #队列是否为满 print(q.qsize()) #队列中的数据个数 """ False True 2 """ q.task_done() # 在完成一项工作后,向完成任务的队列发送一个信号,由q.join()接收 q.join() # 实际上是等到列表为空之后在进行其他操作 while True: date = q.get() # q.get_nowait() 相当于 q.get(block=False) print(date) 多进程调用: import multiprocessing import time import os # 调用方式一 def foo(num): # print(num) print("%s......%s" % (num,time.ctime())) if __name__ == "__main__": L = [] for i in range(3): p = multiprocessing.Process(target=foo,args=("1",)) L.append(p) p.start() for j in L: j.join() print("ending........") # 调用方式二 class Myprocess(multiprocessing.Process): def __init__(self): super(Myprocess,self).__init__() def run(self): print("%s......%s" % (self.name,time.ctime())) if __name__ == "__main__": L = [] for i in range(3): p = Myprocess() L.append(p) p.start() for j in L: j.join() print("ending........") # 进程关系 class Myprocess(multiprocessing.Process): def __init__(self): super(Myprocess,self).__init__() def run(self): print("PPID :%s" % (os.getppid())) # 获取进程的父ID print("PID :%s" % os.getpid()) if __name__ == "__main__": L = [] print("PPID :%s" % (os.getppid())) print("PID :%s" % os.getpid()) # for i in range(3): p = Myprocess() p2 = Myprocess() # L.append(p) p.daemon = True # 守护进程 p.start() p2.start() # for j in L: # j.join() p.join() p2.join() print("ending........") 进程间的通信: # 进程间通信的三种方式 import multiprocessing import queue import time # 一 通过进程队列(注意它与线程队列的区别),将进程队列当作参数传给子进程 def foo(q): # q.put("alxe") date = q.get() # 获取进程队列中数据 # time.sleep(1) date["name"] = "alxe" # 对数据进行修改 # print(date) if __name__ == "__main__": q = multiprocessing.Queue() # 创建一个进程队列 d = {"name": "lhf"} q.put(d) print("================") p = multiprocessing.Process(target=foo,args=(q,)) p.start() # 启动子线程对数据进行修改 # p.daemon(True) p.join() # 等待子进程完成修改 print(q.get()) # 在主进程中获取队列数据,看是否被修改 # print(q.get()) """ 特别注意:同一进程队列在子进程中被get的数据,在主进程中无法再被获取 """ # 二 管道 farther.conn,sub.conn = Pipe() 像socket中的通信 def foo(sub_conn): date = sub_conn.recv() print(date) sub_conn.send("nihao") if __name__ == "__main__": main_conn,sub_conn = multiprocessing.Pipe() d = {"name": "alxe"} p = multiprocessing.Process(target=foo,args=(sub_conn,)) p.start() # 启动子线程对数据进行修改 # p.daemon(True) main_conn.send(d) date = main_conn.recv() print(date) """ 管道只能用于进程间的数据发送与接收(数据交互) """ # 三 数据共享 manager with Manager() as manager: ''' 以上两种方法只实现了数据交互,没有真正实现数据共享 support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array. ''' def foo(d,l): d["name"] = "alxe" l.append("wang") print(id(d),id(l)) if __name__ == "__main__": with multiprocessing.Manager() as manger: d = manger.dict() l = manger.list(range(5)) p = multiprocessing.Process(target=foo,args=(d,l,)) p.start() p.join() print(d) print(l) print(id(d),id(l)) 协程: # 协程是面向用户的,在线程和进程中,我们无法控制每个线程或进程的执行顺序,它们都通过抢占 # 决定执行顺序,协程就是为了解决这个问题 # 协程是由yield来实现的,这是协程的首先回顾一下 # def foo(): # print("ok1") # s = yield 4 # 两步操作,1.执行到这一句return 4 2.s = f.send的值 # print(s) # # print("ok2") # f = foo() # # 我们定义了一个生成器(注意不是函数了),要想执行,需要next方法 # #方法一: # # f.__next__() # #方法二 # # next(f) # #方法三 # date = f.__next__() # 可以发送一个参数,由对应的yield接收 # print(date) # f.send(6) # yield的赋值操作是在执行到yield之后的下一次next中 # 下面再用yield的方法实现之前的生产者消费者模型 # import time # # def consumer(n): # print("顾客 %s 来到包子铺" % n) # while True: # m = yield # print("\033[32;1m顾客%s 吃掉了%s\033[0m" % (n,m)) # def producer(con1,con2): # next(con1) # next(con2) # i = 0 # while True: # time.sleep(1) # print("producer 开始做包子 %s %s" % (i,i+1)) # con1.send(i) # con2.send(i+1) # i += 2 # # if __name__ == "__main__": # con1 = consumer(1) # con2 = consumer(2) # producer(con1,con2) # 用greenlet实现函数间的切换,不需先将函数声明为生成器 # from greenlet import greenlet # def foo1(): # print(1) # g2.switch() # print(2) # # def foo2(): # print(3) # g1.switch() # print(4) # # g1 = greenlet(foo1) # g2 = greenlet(foo2) # g1.switch() # import asyncio # async def foo1(): # await asyncio.sleep(1) # print("1") # # async def main(): # test1 = asyncio.create_task(foo1()) # test2 = asyncio.create_task(foo1()) # test3 = asyncio.create_task(foo1()) # test4 = asyncio.create_task(foo1()) # test5 = asyncio.create_task(foo1()) # test6 = asyncio.create_task(foo1()) # # await test1 # await test2 # await test3 # await test4 # await test5 # await test6 # 执行6次也是等待1秒 # asyncio.run(main()) # import asyncio # async def foo1(i): # await asyncio.sleep(1) # print(i) # loop = asyncio.get_event_loop() # myfun_list = (foo1(i) for i in range(100)) # print(myfun_list) # loop.run_until_complete(asyncio.gather(*myfun_list)) # import asyncio # async def foo1(name): # await asyncio.sleep(1) # print(name) # async def main(foo1): # myfun_list = (foo1(i) for i in range(100)) # print(myfun_list) # await asyncio.gather(*myfun_list) # asyncio.run(main(foo1)) # # import asyncio # async def foo1(name): # await asyncio.sleep(1) # print(name) # async def main(): # await asyncio.gather( # foo1("who"), # foo1("is"), # foo1("alxe"), # ) # asyncio.run(main()) def f(i): print(i) (f(i) for i in range(1)).__next__() (f(i) for i in range(1)).__next__() 进程池和线程池: from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor import requests """ 多进程和多线程在代码上只是生成的pool不同,一个进程执行时,系统会为其分配必要的资源(地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据), 当一个进程下有多个线程时,因为GIL锁(全局解释器锁)的限制,线程在执行时只能依次(或让步)由同一CPU执行, 此时如果线程执行的是计算任务,由于计算必须由CPU计算,所以线程只能依次执行,多线程变得没有意义 这样计算密集的任务就应该由多进程执行,它不受GIL锁的限制 IO密集型的任务就使用多线程,因为询问CPU几乎不耗时 """ pool = ThreadPoolExecutor(3) url_list = [ "https://www.baidu.com", "https://www.zhihu.com", "https://www.bilibili.com/", "https://www.cnblogs.com/", "https://www.iconfont.cn/collections/index?spm=a313x.7781069.1998910419.7&type=3", "https://www.bootcss.com/", "http://jquery.cuishifeng.cn/" ] def visit_url(url): response = requests.request( method="GET", url=url, headers={'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'} ) print(url,response.status_code) return response def done_callback(response,*args,**kwargs): print(response) for url in url_list: v = pool.submit(visit_url,url) v.add_done_callback(done_callback) pool.shutdown(wait=True)