threading 模块和 multiprocessing 模块在使用层面,有很大的相似性。 二、开启多线程的两种方式 1. 创建线程的开销比创建进程的开销小,因而创建线程的速度快 from multiprocessing import Process from threading import Thread import os import time def work(): print('<%s> is running'%os.getpid()) time.sleep(2) print('<%s> is done'%os.getpid()) if __name__ == '__main__': t=Thread(target=work,) # t= Process(target=work,) t.start() 开启进程的第一种方式 from threading import Thread import time class Work(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): # time.sleep(2) print('%s say hell'%self.name) if __name__ == '__main__': t = Work('egon') t.start() 开启线程的第二种方式(用类) 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别 from multiprocessing import Process from threading import Thread import time def work(): time.sleep(2) print('hello') if __name__ == '__main__': t = Thread(target=work)# 如果等上几秒,他会在开启的过程中先打印主,如果不等会先打印 hello function(){ //外汇经济商出入金流程:http://www.fx61.com/support # t = Process(target=work) # 子进程会先打印主, t.start() 线程的开启速度大于进程的开启速度 # 2.---------- from multiprocessing import Process from threading import Thread import os def work(): print('hello',os.getpid()) if __name__ == '__main__': # 在主进程下开启多个线程,每个线程都跟主进程的 pid 一样 t1= Thread(target=work) t2 = Thread(target=work) t1.start() t2.start() print(' 主线程 pid',os.getpid()) p1 = Process(target=work) p2 = Process(target=work) p1.start() p2.start() print(' 主进程 pid', os.getpid()) 在同一个进程下开多个进程和开多个线程的 pid 的不同 from threading import Thread from multiprocessing import Process import os def work(): global n n-=1 n = 100 if __name__ == '__main__': # p = Process(target=work) p = Thread(target=work) # 当开启的是线程的时候,因为同一进程内的线程之间共享进程内的数据 p.start() p.join() print(' 主 ',n) # 毫无疑问子进程 p 已经将自己的全局的 n 改成了 0, # 但改的仅仅是它自己的 , 查看父进程的 n 仍然为 100 同一进程内的线程共享该进程的数据 进程之间是互相隔离的,不共享。需要借助第三方来完成共享(借助队列,管道,共享数据) 三、练习 练习一:多线程实现并发 from socket import * from threading import Thread s = socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 端口重用 s.bind(('127.0.0.1',8081)) s.listen(5) print('start running...') def talk(coon,addr): try: cmd = coon.recv(1024) print(cmd.decode('utf-8')) if not cmd: break coon.send(cmd.upper()) print(' 发送的是 %s'%cmd.upper().decode('utf-8')) except Exception: break coon.close() if __name__ == '__main__': coon,addr = s.accept() print(coon,addr) p =Thread(target=talk,args=(coon,addr)) p.start() s.close() 服务端 from socket import * c = socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8081)) while True: cmd = input('>>:').strip() if not cmd:continue c.send(cmd.encode('utf-8')) data = c.recv(1024) print(' 接受的是 %s'%data.decode('utf-8')) c.close() 客户端 练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件 from threading import Thread import os input_l = [] format_l = [] while True: cmd = input('>>:').strip() if not cmd:continue input_l.append(cmd) def format(): while True: if input_l: format_l.append(res.upper()) # 取出来后变大写 def save(): while True: if format_l: # 如果 format_l 不为空 with open('db','a') as f: f.write(format_l.pop()+'\n') # 写进文件 f.flush() if __name__ == '__main__': t1=Thread(target=talk) t2=Thread(target=format) t3=Thread(target=save) t1.start() t2.start() t3.start() 答案 四、多线程共享同一个进程内的地址空间 from threading import Thread from multiprocessing import Process import os n = 100 def talk(): global n n-=100 print(n) if __name__ == '__main__': t = Thread(target=talk) # 如果开启的是线程的话, n=0 # t = Process(target=talk) # 如果开启的是进程的话, n=100 t.start() t.join() 五、线程对象的其他属性和方法 # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的 list 。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与 len(threading.enumerate()) 有相同的结果。 from threading import Thread from multiprocessing import Process import time,os,threading def work(): time.sleep(2) print('%s is running' % threading.currentThread().getName()) print(threading.current_thread()) # 其他线程 print(threading.currentThread().getName()) # 得到其他线程的名字 if __name__ == '__main__': t = Thread(target=work) t.start() print(threading.current_thread().getName()) # 主线程的名字 print(threading.current_thread()) # 主线程 print(threading.enumerate()) # 连同主线程在内有两个运行的线程 time.sleep(2) print(t.is_alive()) # 判断线程是否存活 print(threading.activeCount()) 线程的其他属性和方法 主进程等所有的非守护的子进程结束他才结束(回收它子进程的资源):(有父子关系) 主线程等非守护线程全都结束它才结束: (没父子关系) from threading import Thread import time,os def talk(): time.sleep(3) print('%s is running..'%os.getpid()) if __name__ == '__main__': t = Thread(target=talk) t.start() join 守护线程与守护进程的区别 1. 守护进程:主进程会等到所有的非守护进程结束,才销毁守护进程。也就是说(主进程运行完了被守护的那个就干掉了) 2. 守护线程:主线程运行完了守护的那个还没有干掉,主线程等非守护线程全都结束它才结束 from multiprocessing import Process from threading import Thread,currentThread import time,os def talk1(): time.sleep(2) print('hello') def talk2(): time.sleep(2) print('you see see') if __name__ == '__main__': t1 = Thread(target=talk1) t2 = Thread(target=talk2) # t1 = Process(target=talk1) # t2 = Process(target=talk2) t1.daemon = True t1.start() t2.start() 守护进程和守护线程 from threading import Thread import time def foo(): print(123) # time.sleep(10) # 如果这个等的时间大于下面等的时间,就把不打印 end123 了 time.sleep(2) # 如果这个等的时间小于下面等的时间,就把 end123 也打印了 print('end123') def bar(): print(456) # time.sleep(5) time.sleep(10) print('end456') if __name__ == '__main__': t1 = Thread(target=foo) t2 = Thread(target=bar) t1.daemon = True # 主线程运行完了守护的那个还没有干掉, t1.start() t2.start() print('main---------') 一个诱惑人的例子 1.python GIL ( Global Interpreter Lock) # 全局的解释器锁 5. 谁拿到 GIL 锁就让谁得到 Cpython 解释器的执行权限 6.GIT 锁保护的是 Cpython 解释器数据的安全,而不会保护你自己程序的数据的安全 7.GIL 锁当遇到阻塞的时候,就被迫的吧锁给释放了,那么其他的就开始抢锁了,抢到 后吧值修改了,但是第一个拿到的还在原本拿到的那个数据的那停留着呢,当再次拿 到锁的时候,数据已经修改了,而你还拿的原来的,这样就混乱了,所以也就保证不了 数据的安全了。 同步锁 GIL 与 Lock 是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显 GIL 不负责这件事,只能用户自定义加锁处理,即 Lock 过程分析:所有线程抢的是 GIL 锁,或者说所有线程抢的是执行权限 线程 1 抢到 GIL 锁,拿到执行权限,开始执行,然后加了一把 Lock ,还没有执行完毕,即线程 1 还未释放 Lock ,有可能线程 2 抢到 GIL 锁,开始执行,执行过程中发现 Lock 还没有被线程 1 释放,于是线程 2 进入阻塞,被夺走执行权限,有可能线程 1 拿到 GIL ,然后正常执行到释放 Lock 。。。这就导致了串行运行的效果 既然是串行,那我们执行 t1.start() t1.join t2.start() t2.join() 这也是串行执行啊,为何还要加 Lock 呢,需知 join 是等待 t1 所有的代码执行完,相当于锁住了 t1 的所有代码,而 Lock 只是锁住一部分操作共享数据的代码。 因为 Python 解释器帮你自动定期进行内存回收,你可以理解为 python 解释器里有一个独立的线程,每过一段时间它起 wake up 做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py 解释器自己的线程是并发运行的,假设你的线程删除了一个变量, py 解释器的垃圾回收线程在清空这个变量的过程中的 clearing 时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题, python 解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是 Python 早期版本的遗留问题。 from threading import Thread,Lock import time n=100 def work(): mutex.acquire() global n temp=n time.sleep(0.01) n=temp-1 mutex.release() if __name__ == '__main__': mutex=Lock() t_l=[] s=time.time() for i in range(100): t=Thread(target=work) t_l.append(t) t.start() for t in t_l: t.join() print('%s:%s' %(time.time()-s,n)) 全局解释锁 锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个 Lock 对象,当你需要访问该资源时,调用 acquire 方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用 release 方法释放锁: import threading mutex = threading.Lock() mutex.aquire() ''' 对公共数据的操作 ''' mutex.release() 锁的格式 2 2 1.100 个线程去抢 GIL 锁,即抢执行权限 3 3 2. 肯定有一个线程先抢到 GIL (暂且称为线程 1 ),然后开始执行,一旦执行就会拿到 lock.acquire() 4 4 3. 极有可能线程 1 还未运行完毕,就有另外一个线程 2 抢到 GIL ,然后开始运行,但线程 2 发现互斥锁 lock 还未被线程 1 释放,于是阻塞,被迫交出执行权限,即释放 GIL 5 5 4. 直到线程 1 重新抢到 GIL ,开始从上次暂停的位置继续执行,直到正常释放互斥锁 lock ,然后其他的线程再重复 2 3 4 的过程 如果不加锁:并发执行,速度快,数据不安全。 加锁:串行执行,速度慢,数据安全。 from threading import current_thread,Thread,Lock import os,time def task(): global n print('%s is running' %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print(' 主 :%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 is running Thread-2 is running ...... Thread-100 is running 主 :0.5216062068939209 n:99 ''' # 不加锁 : 未加锁部分并发执行 , 加锁部分串行执行 , 速度慢 , 数据安全 from threading import current_thread,Thread,Lock import os,time def task(): time.sleep(3) print('%s start to run' %current_thread().getName()) global n lock.acquire() temp=n time.sleep(0.5) n=temp-1 lock.release() if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print(' 主 :%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 is running Thread-2 is running ...... Thread-100 is running ''' # 有的同学可能有疑问 : 既然加锁会让运行变成串行 , 那么我在 start 之后立即使用 join, 就不用加锁了啊 , 也是串行的效果啊 # 没错 : 在 start 之后立刻使用 jion, 肯定会将 100 个任务的执行变成串行 , 毫无疑问 , 最终 n 的结果也肯定是 0, 是安全的 , 但问题是 #start 后立即 join: 任务内的所有代码都是串行执行的 , 而加锁 , 只是加锁的部分即修改共享数据的部分是串行的 # 单从保证数据安全方面 , 二者都可以实现 , 但很明显是加锁的效率更高 . from threading import current_thread,Thread,Lock import os,time def task(): time.sleep(3) print('%s start to run' %current_thread().getName()) global n temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() start_time=time.time() for i in range(100): t=Thread(target=task) t.start() t.join() stop_time=time.time() print(' 主 :%s n:%s' %(stop_time-start_time,n)) ''' Thread-1 start to run Thread-2 start to run ...... Thread-100 start to run 主 :350.6937336921692 n:0 # 耗时是多么的恐怖 ''' 原文链接: https://blog.csdn.net/weixin_44101783/article/details/103002067 |