并发编程之多线程
一多线程的概念介绍
threading模块介绍
threading模块和multiprocessing模块在使用层面,有很大的相似性。
二、开启多线程的两种方式
1 1.创建线程的开销比创建进程的开销小,因而创建线程的速度快 2 from multiprocessing import Process 3 from threading import Thread 4 import os 5 import time 6 def work(): 7 print('<%s> is running'%os.getpid()) 8 time.sleep(2) 9 print('<%s> is done'%os.getpid()) 10 11 if __name__ == '__main__': 12 t=Thread(target=work,) 13 # t= Process(target=work,) 14 t.start() 15 print('主',os.getpid())
1 from threading import Thread 2 import time 3 class Work(Thread): 4 def __init__(self,name): 5 super().__init__() 6 self.name = name 7 def run(self): 8 # time.sleep(2) 9 print('%s say hell'%self.name) 10 if __name__ == '__main__': 11 t = Work('egon') 12 t.start() 13 print('主')
在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
1 from multiprocessing import Process 2 from threading import Thread 3 import time 4 def work(): 5 time.sleep(2) 6 print('hello') 7 if __name__ == '__main__': 8 t = Thread(target=work)#如果等上几秒,他会在开启的过程中先打印主,如果不等会先打印hello 9 # t = Process(target=work) #子进程会先打印主, 10 t.start() 11 print('主') 12
1 # 2.---------- 2 from multiprocessing import Process 3 from threading import Thread 4 import os 5 def work(): 6 print('hello',os.getpid()) 7 if __name__ == '__main__': 8 #在主进程下开启多个线程,每个线程都跟主进程的pid一样 9 t1= Thread(target=work) 10 t2 = Thread(target=work) 11 t1.start() 12 t2.start() 13 print('主线程pid',os.getpid()) 14 15 #来多个进程,每个进程都有不同的pid 16 p1 = Process(target=work) 17 p2 = Process(target=work) 18 p1.start() 19 p2.start() 20 print('主进程pid', os.getpid())
1 from threading import Thread 2 from multiprocessing import Process 3 import os 4 def work(): 5 global n 6 n-=1 7 print(n) #所以被改成99了 8 n = 100 9 if __name__ == '__main__': 10 # p = Process(target=work) 11 p = Thread(target=work) #当开启的是线程的时候,因为同一进程内的线程之间共享进程内的数据 12 #所以打印的n为99 13 p.start() 14 p.join() 15 print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0, 16 # 但改的仅仅是它自己的,查看父进程的n仍然为100
进程之间是互相隔离的,不共享。需要借助第三方来完成共享(借助队列,管道,共享数据)
练习
多线程实现并发
1 from socket import * 2 from threading import Thread 3 s = socket(AF_INET,SOCK_STREAM) 4 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #端口重用 5 s.bind(('127.0.0.1',8081)) 6 s.listen(5) 7 print('start running...') 8 def talk(coon,addr): 9 while True: # 通信循环 10 try: 11 cmd = coon.recv(1024) 12 print(cmd.decode('utf-8')) 13 if not cmd: break 14 coon.send(cmd.upper()) 15 print('发送的是%s'%cmd.upper().decode('utf-8')) 16 except Exception: 17 break 18 coon.close() 19 if __name__ == '__main__': 20 while True:#链接循环 21 coon,addr = s.accept() 22 print(coon,addr) 23 p =Thread(target=talk,args=(coon,addr)) 24 p.start() 25 s.close()
1 from socket import * 2 c = socket(AF_INET,SOCK_STREAM) 3 c.connect(('127.0.0.1',8081)) 4 while True: 5 cmd = input('>>:').strip() 6 if not cmd:continue 7 c.send(cmd.encode('utf-8')) 8 data = c.recv(1024) 9 print('接受的是%s'%data.decode('utf-8')) 10 c.close()
三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
1 from threading import Thread 2 import os 3 input_l = [] 4 format_l = [] 5 def talk(): #监听输入任务 6 while True: 7 cmd = input('>>:').strip() 8 if not cmd:continue 9 input_l.append(cmd) 10 11 def format(): 12 while True: 13 if input_l: 14 res = input_l.pop()#取出来 15 format_l.append(res.upper()) #取出来后变大写 16 def save(): 17 while True: 18 if format_l: #如果format_l不为空 19 with open('db','a') as f: 20 f.write(format_l.pop()+'\n') #写进文件 21 f.flush() 22 if __name__ == '__main__': 23 t1=Thread(target=talk) 24 t2=Thread(target=format) 25 t3=Thread(target=save) 26 t1.start() 27 t2.start() 28 t3.start()
四、多线程共享同一个进程内的地址空间
1 from threading import Thread 2 from multiprocessing import Process 3 import os 4 n = 100 5 def talk(): 6 global n 7 n-=100 8 print(n) 9 if __name__ == '__main__': 10 t = Thread(target=talk) #如果开启的是线程的话,n=0 11 # t = Process(target=talk) #如果开启的是进程的话,n=100 12 t.start() 13 t.join() 14 print('主',n)
五、线程对象的其他属性和方法
Thread实例对象的方法
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。
threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
1 from threading import Thread 2 from multiprocessing import Process 3 import time,os,threading 4 def work(): 5 time.sleep(2) 6 print('%s is running' % threading.currentThread().getName()) 7 print(threading.current_thread()) #其他线程 8 print(threading.currentThread().getName()) #得到其他线程的名字 9 if __name__ == '__main__': 10 t = Thread(target=work) 11 t.start() 12 13 print(threading.current_thread().getName()) #主线程的名字 14 print(threading.current_thread()) #主线程 15 print(threading.enumerate()) #连同主线程在内有两个运行的线程 16 time.sleep(2) 17 print(t.is_alive()) #判断线程是否存活 18 print(threading.activeCount()) 19 print('主')
六、join与守护线程
主进程等所有的非守护的子进程结束他才结束(回收它子进程的资源):(有父子关系)
主线程等非守护线程全都结束它才结束: (没父子关系)
1 from threading import Thread 2 import time,os 3 def talk(): 4 time.sleep(3) 5 print('%s is running..'%os.getpid()) 6 if __name__ == '__main__': 7 t = Thread(target=talk) 8 t.start() 9 t.join() #主进程在等子进程结束 10 print('主')
守护线程与守护进程的区别
1.守护进程:主进程会等到所有的非守护进程结束,才销毁守护进程。也就是说(主进程运行完了被守护的那个就干掉了)
2.守护线程:主线程运行完了守护的那个还没有干掉,主线程等非守护线程全都结束它才结束
1 from multiprocessing import Process 2 from threading import Thread,currentThread 3 import time,os 4 def talk1(): 5 time.sleep(2) 6 print('hello') 7 def talk2(): 8 time.sleep(2) 9 print('you see see') 10 if __name__ == '__main__': 11 t1 = Thread(target=talk1) 12 t2 = Thread(target=talk2) 13 # t1 = Process(target=talk1) 14 # t2 = Process(target=talk2) 15 t1.daemon = True 16 t1.start() 17 t2.start() 18 print('主线程',os.getpid())
1 #3 --------迷惑人的例子 2 from threading import Thread 3 import time 4 def foo(): 5 print(123) 6 # time.sleep(10) #如果这个等的时间大于下面等的时间,就把不打印end123了 7 time.sleep(2) #如果这个等的时间小于下面等的时间,就把end123也打印了 8 print('end123') 9 def bar(): 10 print(456) 11 # time.sleep(5) 12 time.sleep(10) 13 print('end456') 14 if __name__ == '__main__': 15 t1 = Thread(target=foo) 16 t2 = Thread(target=bar) 17 t1.daemon = True #主线程运行完了守护的那个还没有干掉, 18 # 主线程等非守护线程全都结束它才结束 19 t1.start() 20 t2.start() 21 print('main---------')
七、GIL与Lock
1.python GIL(Global Interpreter Lock) #全局的解释器锁
2.锁的目的:牺牲了效率,保证了数据的安全
3.保护不同的数据加不同的锁()
4.python自带垃圾回收
5.谁拿到GIL锁就让谁得到Cpython解释器的执行权限
6.GIT锁保护的是Cpython解释器数据的安全,而不会保护你自己程序的数据的安全
7.GIL锁当遇到阻塞的时候,就被迫的吧锁给释放了,那么其他的就开始抢锁了,抢到
后吧值修改了,但是第一个拿到的还在原本拿到的那个数据的那停留着呢,当再次拿
到锁的时候,数据已经修改了,而你还拿的原来的,这样就混乱了,所以也就保证不了
数据的安全了。
8.那么怎么解决数据的安全ne ?
自己再给加吧锁:mutex=Lock()
同步锁
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早期版本的遗留问题
1 from threading import Thread,Lock 2 import time 3 n=100 4 def work(): 5 mutex.acquire() 6 global n 7 temp=n 8 time.sleep(0.01) 9 n=temp-1 10 mutex.release() 11 if __name__ == '__main__': 12 mutex=Lock() 13 t_l=[] 14 s=time.time() 15 for i in range(100): 16 t=Thread(target=work) 17 t_l.append(t) 18 t.start() 19 for t in t_l: 20 t.join() 21 print('%s:%s' %(time.time()-s,n))
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
1 import threading 2 mutex = threading.Lock() 3 mutex.aquire() 4 ''' 5 对公共数据的操作 6 ''' 7 mutex.release()
1 分析: 2 1.100个线程去抢GIL锁,即抢执行权限 3 2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire() 4 3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL 5 4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
如果不加锁:并发执行,速度快,数据不安全。
加锁:串行执行,速度慢,数据安全。
1 #不加锁:并发执行,速度快,数据不安全 2 from threading import current_thread,Thread,Lock 3 import os,time 4 def task(): 5 global n 6 print('%s is running' %current_thread().getName()) 7 temp=n 8 time.sleep(0.5) 9 n=temp-1 10 11 12 if __name__ == '__main__': 13 n=100 14 lock=Lock() 15 threads=[] 16 start_time=time.time() 17 for i in range(100): 18 t=Thread(target=task) 19 threads.append(t) 20 t.start() 21 for t in threads: 22 t.join() 23 24 stop_time=time.time() 25 print('主:%s n:%s' %(stop_time-start_time,n)) 26 27 ''' 28 Thread-1 is running 29 Thread-2 is running 30 ...... 31 Thread-100 is running 32 主:0.5216062068939209 n:99 33 ''' 34 35 36 #不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全 37 from threading import current_thread,Thread,Lock 38 import os,time 39 def task(): 40 #未加锁的代码并发运行 41 time.sleep(3) 42 print('%s start to run' %current_thread().getName()) 43 global n 44 #加锁的代码串行运行 45 lock.acquire() 46 temp=n 47 time.sleep(0.5) 48 n=temp-1 49 lock.release() 50 51 if __name__ == '__main__': 52 n=100 53 lock=Lock() 54 threads=[] 55 start_time=time.time() 56 for i in range(100): 57 t=Thread(target=task) 58 threads.append(t) 59 t.start() 60 for t in threads: 61 t.join() 62 stop_time=time.time() 63 print('主:%s n:%s' %(stop_time-start_time,n)) 64 65 ''' 66 Thread-1 is running 67 Thread-2 is running 68 ...... 69 Thread-100 is running 70 主:53.294203758239746 n:0 71 ''' 72 73 #有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊 74 #没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是 75 #start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的 76 #单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高. 77 from threading import current_thread,Thread,Lock 78 import os,time 79 def task(): 80 time.sleep(3) 81 print('%s start to run' %current_thread().getName()) 82 global n 83 temp=n 84 time.sleep(0.5) 85 n=temp-1 86 87 88 if __name__ == '__main__': 89 n=100 90 lock=Lock() 91 start_time=time.time() 92 for i in range(100): 93 t=Thread(target=task) 94 t.start() 95 t.join() 96 stop_time=time.time() 97 print('主:%s n:%s' %(stop_time-start_time,n)) 98 99 ''' 100 Thread-1 start to run 101 Thread-2 start to run 102 ...... 103 Thread-100 start to run 104 主:350.6937336921692 n:0 #耗时是多么的恐怖 105 '''
posted on 2018-03-23 18:00 WorthWaitingFor 阅读(174) 评论(0) 编辑 收藏 举报