python并发编程之多线程理论

1、什么是线程?

  进程只是用来把资源集中到一起(进程是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。

  多线程(多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间。

2、线程的创建开销小

  创建一个进程,需要申请空间

  创建线程,无需申请空间,所以开销小

3、线程与进程的区别

  1. Threads share the address space of the process that created it; processes have their own address space.
  2. Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
  3. Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
  4. New threads are easily created; new processes require duplication of the parent process.
  5. Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
  6. Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

4、为何要用多线程?

  多线程指的是,在一个进程中开启多个线程,简单的将:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细阐述:

    1、多线程共享一个进程的地址空间;

    2、线程比进程更轻量级,线程比进程更容易创建和撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这个特性很有用;

    3、若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度;

    4、在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不使用python)

python并发编程之多线程

1、threading模块

2、开启线程的两种方式

  1、方式一

  1. #方式一
  2. from threading import Thread
  3. import time
  4. def sayhi(name):
  5. time.sleep(2)
  6. print('%s say hello' %name)
  7.  
  8. if __name__ == '__main__':
  9. t=Thread(target=sayhi,args=('egon',))
  10. t.start()
  11. print('主线程')

  2、方式二

  1. from threading import Thread
  2. import time
  3. class Sayhi(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 hello' % self.name)
  10.  
  11. if __name__ == '__main__':
  12. t = Sayhi('egon')
  13. t.start()
  14. print('主线程')

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

  谁的开启速度快

  1. from threading import Thread
  2. from multiprocessing import Process
  3. import os
  4.  
  5. def work():
  6. print('hello')
  7.  
  8. if __name__ == '__main__':
  9. #在主进程下开启线程
  10. t=Thread(target=work)
  11. t.start()
  12. print('主线程/主进程')
  13. '''
  14. 打印结果:
  15. hello
  16. 主线程/主进程
  17. '''
  18.  
  19. #在主进程下开启子进程
  20. t=Process(target=work)
  21. t.start()
  22. print('主线程/主进程')
  23. '''
  24. 打印结果:
  25. 主线程/主进程
  26. hello
  27. '''

  

  1. from threading import Thread
  2. from multiprocessing import Process
  3. import os
  4.  
  5. def work():
  6. print('hello',os.getpid())
  7.  
  8. if __name__ == '__main__':
  9. #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
  10. t1=Thread(target=work)
  11. t2=Thread(target=work)
  12. t1.start()
  13. t2.start()
  14. print('主线程/主进程pid',os.getpid())
  15.  
  16. #part2:开多个进程,每个进程都有不同的pid
  17. p1=Process(target=work)
  18. p2=Process(target=work)
  19. p1.start()
  20. p2.start()
  21. 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=0
  7.  
  8. if __name__ == '__main__':
  9. # n=100
  10. # p=Process(target=work)
  11. # p.start()
  12. # p.join()
  13. # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
  14.  
  15. n=1
  16. t=Thread(target=work)
  17. t.start()
  18. t.join()
  19. print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据

3、线程相关的其他方法

  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. import threading
  3. from multiprocessing import Process
  4. import os
  5.  
  6. def work():
  7. import time
  8. time.sleep(3)
  9. print(threading.current_thread().getName())
  10.  
  11. if __name__ == '__main__':
  12. #在主进程下开启线程
  13. t=Thread(target=work)
  14. t.start()
  15.  
  16. print(threading.current_thread().getName())
  17. print(threading.current_thread()) #主线程
  18. print(threading.enumerate()) #连同主线程在内有两个运行的线程
  19. print(threading.active_count())
  20. print('主线程/主进程')
  21.  
  22. '''
  23. 打印结果:
  24. MainThread
  25. <_MainThread(MainThread, started 140735268892672)>
  26. [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]
  27. 主线程/主进程
  28. Thread-1
  29. '''

主线程等待子线程结束

  1. from threading import Thread
  2. import time
  3. def sayhi(name):
  4. time.sleep(2)
  5. print('%s say hello' %name)
  6.  
  7. if __name__ == '__main__':
  8. t=Thread(target=sayhi,args=('egon',))
  9. t.start()
  10. t.join()
  11. print('主线程')
  12. print(t.is_alive())
  13. '''
  14. egon say hello
  15. 主线程
  16. False
  17. '''

4、守护线程

  无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

  需要强调的是:运行完毕并非终止运行

  1. #1.对主进程来说,运行完毕指的是主进程代码运行完毕
  2.  
  3. #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
  1. #1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
  2.  
  3. #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
  1. from threading import Thread
  2. import time
  3. def sayhi(name):
  4. time.sleep(2)
  5. print('%s say hello' %name)
  6.  
  7. if __name__ == '__main__':
  8. t=Thread(target=sayhi,args=('egon',))
  9. t.setDaemon(True) #必须在t.start()之前设置
  10. t.start()
  11.  
  12. print('主线程')
  13. print(t.is_alive())
  14. '''
  15. 主线程
  16. True
  17. '''

python GIL

1、介绍

  1. '''
  2. 定义:
  3. In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
  4. native threads from executing Python bytecodes at once. This lock is necessary mainly
  5. because CPython’s memory management is not thread-safe. (However, since the GIL
  6. exists, other features have grown to depend on the guarantees that it enforces.)
  7. '''
  8. 结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

2、GIL介绍(Global Interpreter Lock)

  GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都是一样的,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

  保护不同的数据的安全,就应该加不同的锁

  每次执行python程序,都会产生一个独立的进程。

  

  1. '''
  2. #验证python test.py只会产生一个进程
  3. #test.py内容
  4. import os,time
  5. print(os.getpid())
  6. time.sleep(1000)
  7. '''
  8. python3 test.py
  9. #在windows下
  10. tasklist |findstr python
  11. #在linux下
  12. ps aux |grep python

  在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有的线程都运行在这一个进程内。

  

  1. #1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
  2. 例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。
  3.  
  4. #2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

  综上,如果多个线程的target=work,那么执行流程是:

  多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码执行。

  解释器的代码是所有线程共享的。所以垃圾回收线程也可能访问到解释器的代码而去执行。

三、GIL与Lock

  GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理:

  

四、GIL与多线程

  有了GIL的存在,同一时刻同一进程中只有一个线程被执行。

  对计算来说,cpu越多越好,但是对于I/O来说,再多的CPU也没用

  

  1. #分析:
  2. 我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
  3. 方案一:开启四个进程
  4. 方案二:一个进程下,开启四个线程
  5.  
  6. #单核情况下,分析结果:
  7.   如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  8.   如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
  9.  
  10. #多核情况下,分析结果:
  11.   如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
  12.   如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
  13.  
  14. #结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

五、多线程性能测试

计算密集型:多线程效率高

  1. from multiprocessing import Process
  2. from threading import Thread
  3. import os,time
  4. def work():
  5. res=0
  6. for i in range(100000000):
  7. res*=i
  8.  
  9. if __name__ == '__main__':
  10. l=[]
  11. print(os.cpu_count()) #本机为4核
  12. start=time.time()
  13. for i in range(4):
  14. p=Process(target=work) #耗时5s多
  15. p=Thread(target=work) #耗时18s多
  16. l.append(p)
  17. p.start()
  18. for p in l:
  19. p.join()
  20. stop=time.time()
  21. print('run time is %s' %(stop-start))

I/O密集型:多线程效率高

  1. from multiprocessing import Process
  2. from threading import Thread
  3. import threading
  4. import os,time
  5. def work():
  6. time.sleep(2)
  7. print('===>')
  8.  
  9. if __name__ == '__main__':
  10. l=[]
  11. print(os.cpu_count()) #本机为4核
  12. start=time.time()
  13. for i in range(400):
  14. # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
  15. p=Thread(target=work) #耗时2s多
  16. l.append(p)
  17. p.start()
  18. for p in l:
  19. p.join()
  20. stop=time.time()
  21. print('run time is %s' %(stop-start))

应用:

  多线程用户I/O密集型,如socket,爬虫,web

  多进程用于计算密集型,如金融分析

  

paramiko模块

1、SSHClient

基于用户名密码:

  1. import paramiko
  2.  
  3. # 创建SSH对象
  4. ssh = paramiko.SSHClient()
  5. # 允许连接不在know_hosts文件中的主机
  6. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  7. # 连接服务器
  8. ssh.connect(hostname='120.92.84.249', port=22, username='root', password='xxx')
  9.  
  10. # 执行命令
  11. stdin, stdout, stderr = ssh.exec_command('df')
  12. # 获取命令结果
  13. result = stdout.read()
  14. print(result.decode('utf-8'))
  15. # 关闭连接
  16. ssh.close()

基于公钥密钥连接:

  1. import paramiko
  2.  
  3. private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa')
  4.  
  5. # 创建SSH对象
  6. ssh = paramiko.SSHClient()
  7. # 允许连接不在know_hosts文件中的主机
  8. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  9. # 连接服务器
  10. ssh.connect(hostname='120.92.84.249', port=22, username='root', pkey=private_key)
  11.  
  12. # 执行命令
  13. stdin, stdout, stderr = ssh.exec_command('df')
  14. # 获取命令结果
  15. result = stdout.read()
  16. print(result.decode('utf-8'))
  17. # 关闭连接
  18. ssh.close()

SFTPClient:

基于用户名和密码:

  1. import paramiko
  2.  
  3. transport = paramiko.Transport(('120.92.84.249',22))
  4. transport.connect(username='root',password='xxx')
  5.  
  6. sftp = paramiko.SFTPClient.from_transport(transport)
  7. # 将location.py 上传至服务器 /tmp/test.py
  8. sftp.put('/tmp/id_rsa', '/etc/test.rsa')
  9. # 将remove_path 下载到本地 local_path
  10. sftp.get('remove_path', 'local_path')
  11.  
  12. transport.close()

基于公钥密钥上传下载:

  1. import paramiko
  2.  
  3. private_key = paramiko.RSAKey.from_private_key_file('/tmp/id_rsa')
  4.  
  5. transport = paramiko.Transport(('120.92.84.249', 22))
  6. transport.connect(username='root', pkey=private_key )
  7.  
  8. sftp = paramiko.SFTPClient.from_transport(transport)
  9. # 将location.py 上传至服务器 /tmp/test.py
  10. sftp.put('/tmp/id_rsa', '/tmp/a.txt')
  11. # 将remove_path 下载到本地 local_path
  12. sftp.get('remove_path', 'local_path')
  13.  
  14. transport.close()

Python20 - Day09的更多相关文章

  1. DAY09、函数

    一.函数的定义:跟变量名的定义大同小异 1.声明函数的关键词:def 2.函数(变量)名:使用函数的依据 3.参数列表:()   参数个数可以为0到n个,但()一定不能丢,完成功能的必要条件 4.函数 ...

  2. Python异常处理和进程线程-day09

    写在前面 上课第九天,打卡: 最坏的结果,不过是大器晚成: 一.异常处理 - 1.语法错误导致的异常 - 这种错误,根本过不了python解释器的语法检测,必须在程序运行前就修正: - 2.逻辑上的异 ...

  3. day09:Servlet详解

        day09 Servlet概述 生命周期方法: void init(ServletConfig):出生之后(1次): void service(ServletRequest request, ...

  4. python开发学习-day09(队列、多路IO阻塞、堡垒机模块、mysql操作模块)

    s12-20160312-day09 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...

  5. 学习日常笔记<day09>Http协议

    1 Http协议入门 1.1 什么是http协议 http协议: 对浏览器客户端 和  服务器端 之间数据传输的格式规范 1.2 查看http协议的工具 1)使用火狐的firebug插件(右键-> ...

  6. day09——初识函数

    day09 函数的定义 # len() s = 'alexdsb' count = 0 for i in s: count += 1 print(count) s = [1,2,23,3,4,5,6] ...

  7. day09 python函数 返回值 参数

    day09 python   一.函数     1.函数         函数是对功能的封装         语法:         定义函数:             def 函数名(形参):    ...

  8. Python学习day09 - Python进阶(3)

    figure:last-child { margin-bottom: 0.5rem; } #write ol, #write ul { position: relative; } img { max- ...

  9. day09 Django: 组件cookie session

    day09 Django: 组件cookie session   一.cookie和session都是会话跟踪技术     1.什么是会话             可以理解为客户端和服务端之间的一次会 ...

随机推荐

  1. 【css】css实现点击Toggle功能/icon切换

    ①实现Toggle功能 html结构: <div class="cssTog"><label> <p> 专业:B020309现代企业管理(独立本 ...

  2. 参加360前端星计划总结(二)--HTML&CSS

    HTML学习手册(英文版)html:the living standard 重要知识点 文档声明的作用a. 指定html的文档标准和版本b. 告诉浏览器渲染模式,有怪异模式(较为古老的模式,不写文档声 ...

  3. redis 基本数据类型-字符串(String)

    不瘦原来对redis也是有个大概的了解(就你知道的多), 但是最近和大神聊天的过程中才明白自己知道的简直就是鸡毛蒜皮(让你得瑟),所以不瘦打算从头在捋一遍,顺便把过程也记录下来,如果能给大家在学习re ...

  4. 三星S5-PV210内存初始化

    一.S5PV210时钟系统 时钟:一定频率的电信号.   时钟系统:基于CMOS工艺的高性能处理器时钟系统,集成PLL可以从内部触发,比从外部触发更快且更准确,能有效地避免一些与信号完整性相关的问题. ...

  5. 解决kali linux使用metasploit报错办法

    curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit- ...

  6. 大数据入门第十一天——hive详解(一)入门与安装

    一.基本概念 1.什么是hive The Apache Hive ™ data warehouse software facilitates reading, writing, and managin ...

  7. Caused by: java.lang.IllegalArgumentException: Modifying queries can only use void or int/Integer as return type!

    Caused by: java.lang.IllegalArgumentException: Modifying queries can only use void or int/Integer as ...

  8. spring boot 资料

    http://412887952-qq-com.iteye.com/blog/2344171 http://study.163.com/course/courseMain.htm?courseId=1 ...

  9. WPF 主题

    原文:WPF 主题 WPF 切换主题 string packUri = String.Format(@"/WpfControlLibrary1;component/Dictionary1.x ...

  10. 如何搭建openvpn

    一.什么是openvpn Openvpn是一款基于openssl的开源vpn软件,它可以很好的运行在linux及windows各发行版本中,它的核心技术是虚拟网卡,其实它更像是一个底层的网卡驱动软件, ...