一多线程的概念介绍

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())
  16.  
  17. 开启进程的第一种方式
  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('主')
  14.  
  15. 开启线程的第二种方式(用类)

在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

  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.  
  13. 线程的开启速度大于进程的开启速度
  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())
  21.  
  22. 在同一个进程下开多个进程和开多个线程的pid的不同
  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
  17.  
  18. 同一进程内的线程共享该进程的数据

进程之间是互相隔离的,不共享。需要借助第三方来完成共享(借助队列,管道,共享数据)

三、练习

练习一:多线程实现并发

  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()
  26.  
  27. 服务端
  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()
  11.  
  12. 客户端

练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件

  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()
  29.  
  30. 答案

四、多线程共享同一个进程内的地址空间 

  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)

五、线程对象的其他属性和方法

  1. Thread实例对象的方法
  2. # isAlive(): 返回线程是否活动的。
  3. # getName(): 返回线程名。
  4. # setName(): 设置线程名。
  5.  
  6. threading模块提供的一些方法:
  7. # threading.currentThread(): 返回当前的线程变量。
  8. # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  9. # 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('主')
  20.  
  21. 线程的其他属性和方法

六、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('主')
  11.  
  12. join

守护线程与守护进程的区别

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())
  19.  
  20. 守护进程和守护线程
  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---------')
  22.  
  23. 一个诱惑人的例子

七、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))
  22.  
  23. 全局解释锁

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

  1. import threading
  2. mutex = threading.Lock()
  3. mutex.aquire()
  4. '''
  5. 对公共数据的操作
  6. '''
  7. mutex.release()
  8.  
  9. 锁的格式
  1. 分析:
  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的过程

如果不加锁:并发执行,速度快,数据不安全。

加锁:串行执行,速度慢,数据安全。

  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. if __name__ == '__main__':
  12. n=100
  13. lock=Lock()
  14. threads=[]
  15. start_time=time.time()
  16. for i in range(100):
  17. t=Thread(target=task)
  18. threads.append(t)
  19. t.start()
  20. for t in threads:
  21. t.join()
  22.  
  23. stop_time=time.time()
  24. print('主:%s n:%s' %(stop_time-start_time,n))
  25.  
  26. '''
  27. Thread-1 is running
  28. Thread-2 is running
  29. ......
  30. Thread-100 is running
  31. 主:0.5216062068939209 n:99
  32. '''
  33.  
  34. #不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
  35. from threading import current_thread,Thread,Lock
  36. import os,time
  37. def task():
  38. #未加锁的代码并发运行
  39. time.sleep(3)
  40. print('%s start to run' %current_thread().getName())
  41. global n
  42. #加锁的代码串行运行
  43. lock.acquire()
  44. temp=n
  45. time.sleep(0.5)
  46. n=temp-1
  47. lock.release()
  48.  
  49. if __name__ == '__main__':
  50. n=100
  51. lock=Lock()
  52. threads=[]
  53. start_time=time.time()
  54. for i in range(100):
  55. t=Thread(target=task)
  56. threads.append(t)
  57. t.start()
  58. for t in threads:
  59. t.join()
  60. stop_time=time.time()
  61. print('主:%s n:%s' %(stop_time-start_time,n))
  62.  
  63. '''
  64. Thread-1 is running
  65. Thread-2 is running
  66. ......
  67. Thread-100 is running
  68. 主:53.294203758239746 n:0
  69. '''
  70.  
  71. #有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
  72. #没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
  73. #start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
  74. #单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
  75. from threading import current_thread,Thread,Lock
  76. import os,time
  77. def task():
  78. time.sleep(3)
  79. print('%s start to run' %current_thread().getName())
  80. global n
  81. temp=n
  82. time.sleep(0.5)
  83. n=temp-1
  84.  
  85. if __name__ == '__main__':
  86. n=100
  87. lock=Lock()
  88. start_time=time.time()
  89. for i in range(100):
  90. t=Thread(target=task)
  91. t.start()
  92. t.join()
  93. stop_time=time.time()
  94. print('主:%s n:%s' %(stop_time-start_time,n))
  95.  
  96. '''
  97. Thread-1 start to run
  98. Thread-2 start to run
  99. ......
  100. Thread-100 start to run
  101. 主:350.6937336921692 n:0 #耗时是多么的恐怖
  102. '''
  103.  
  104. 互斥锁与join的区别(重点!!!)

利用threading模块开线程的更多相关文章

  1. Python之网路编程利用threading模块开线程

    一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 1 1.创建线程的开销比创建进程的开销 ...

  2. Python使用Threading模块创建线程

    使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法: #!/usr/bin/python # -*- coding: UTF-8 ...

  3. 使用threading模块创建线程

    #_author:来童星#date:2019/12/17#使用threading模块创建线程import threading,timedef process(): for i in range(3): ...

  4. Python——threading模块(线程)

    一.threading模块的对象 Thread:表示一个执行线程的对象 Lock:锁 Rlock:可重入锁对象 Condition:条件变量对象,使得一个线程等待另一个线程满足特定的“条件” Even ...

  5. threading模块创建线程

    什么是线程 (thread) 线程也是一种多任务编程方式,可以使用计算机的多核资源.线程被称为轻量级的进程. 线程特征 *线程计算机多核分配的最小单位 *一个进程可以包含多个线程 *线程也是一个运行的 ...

  6. <python的线程与threading模块>

    <python的线程与threading模块> 一 线程的两种调用方式 threading 模块建立在thread 模块之上.thread模块以低级.原始的方式来处理和控制线程,而thre ...

  7. Python:使用threading模块实现多线程编程

    转:http://blog.csdn.net/bravezhe/article/details/8585437 Python:使用threading模块实现多线程编程一[综述] Python这门解释性 ...

  8. python学习笔记之使用threading模块实现多线程(转)

    综述 Python这门解释性语言也有专门的线程模型,Python虚拟机使用GIL(Global Interpreter Lock,全局解释器锁)来互斥线程对共享资源的访问,但暂时无法利用多处理器的优势 ...

  9. Python之threading模块的使用

    作用:同一个进程空间并发运行多个操作,专业术语简称为:[多线程] 1.任务函数不带参数多线程 #!/usr/bin/env python # -*- coding: utf-8 -*- import ...

随机推荐

  1. Java 复制PPT幻灯片

    本篇文章将介绍通过java程序原样复制PPT幻灯片的方法.这里分两种情况来复制,即 在同一幻灯片文档中复制 在不同幻灯片文档间复制 使用工具:Free Spire.Presentation for J ...

  2. 1026-windy数+数位DP+记忆化搜索

    1026: [SCOI2009]windy数 题意:数位DP模板题: 目前只理解了记忆化搜索,就想练练手, ------给递推写法留一个位子 ------ 注意这道题要判断前导0的情况,1 )可以加一 ...

  3. 杭电多校第十场 hdu6435 CSGO 二进制枚举子集

    CSGO Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)Total Subm ...

  4. poj 3616 Milking Time (基础dp)

    题目链接 http://poj.org/problem?id=3616 题意:在一个农场里,在长度为N个时间可以挤奶,但只能挤M次,且每挤一次就要休息t分钟: 接下来给m组数据表示挤奶的时间与奶量求最 ...

  5. 微信公众号之获取openId

    在小伙伴们开发微信公众号.小程序或者是在微信内置浏览器打开的项目时,会遇到的第一个问题就是如何获取openId,今天小编就给大家带来的是如何获取openId. 首先   我们要从微信开发者后台得到ap ...

  6. [译]Introduction to Concurrency in Spring Boot

    当我们使用springboot构建服务的时候需要处理并发.一种错误的观念认为由于使用了Servlets,它对于每个请求都分配一个线程来处理,所以就没有必要考虑并发.在这篇文章中,我将提供一些建议,用于 ...

  7. Optional和Stream的map与flatMap

    Optional的map和flatMap Optional存在map和flatMap方法.map源码如下 public<U> Optional<U> map(Function& ...

  8. pip的使用

    目录 一.配置pip环境变量 二.Cmd终端使用pip 三.Pycharm使用pip 四.Jupyter使用pip 如果把python假想成一部手机,那么pip就是这部手机上的应用管家/APP,他可以 ...

  9. 虚拟化(一) -VMware产品介绍

    https://www.cnblogs.com/zhrngM/p/9547928.html 由于公司最近在做虚拟化监控,因此就需要把虚拟化方面的知识给学习总结一下,对于虚拟化的概念,摘自百度百科,如下 ...

  10. Docker下kafka学习三部曲之二:本地环境搭建

    在上一章< Docker下kafka学习,三部曲之一:极速体验kafka>中我们快速体验了kafka的消息分发和订阅功能,但是对环境搭建的印象仅仅是执行了几个命令和脚本,本章我们通过实战来 ...