一.GIL锁

什么是GIL? 全局解释器锁,是加在解释器上的互斥锁

GC是python自带的内存管理机制,GC的工作原理:python中的内存管理使用的是应用计数,每个数会被加上一个整型的计数器,表示这个数据被引用的次数,当这个整数变为0时则表示该数据已经没有人使用,成为了垃圾数据,当内存占用达到某个阈值,GC会将其他线程挂起,然后执行垃圾清理操作,垃圾清理也是一串代码,也就需要一条线程来执行.

为什么需要GIL?

由于CPython的内存管理机制是非线程安全,于是CPython就给解释器加了一个锁,解决了安全问题,但是降低了效率,另外,虽然有解决方案,但是由于牵涉太多,一旦修改则很多基于GIL的程序都需要修改,所以变成了历史遗留问题.

GIL加锁,解锁的时机?

加锁时机:在调用解释器时立即加锁

解锁时机:①当前线程遇到IO时释放 ②当前线程执行时间超过设定值时释放,解释器会检测线程的执行时间,一旦到达某个阈值,通知线程保存状态切换线程.

GIL带来的问题:即使是多核处理器下也无法真正的并行.

总结:

①在单核情况下,无论是IO密集型还是计算密集型,GIL都不会产生影响,而多线程开销小,并且节约资源,所以使用多线程.

②在多核情况下,IO密集型会受到GIL的影响,但是很明显IO速度远比计算速度慢,所以两者执行的时间差不多,基本可以忽略不计,而在这个情况下我们考虑到多线程开销小,并且节约资源,所以多核情况下,IO密集型我们使用多线程.

③对于计算密集型,在多核情况下,CPython中多线程是无法并行的,为了解决这一弊端,Python推出了多进程技术,可以良好的利用多核处理器来完成计算的任务.

多线程用于IO密集型,如socket,爬虫,web

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

多进程与多线程效率对比:

  1. 现在的电脑都是多核系统
  2. #多进程解决计算密集型
  3. from multiprocessing import Process
  4. import time
  5. a = 10
  6. def task():
  7. for i in range(10000000):
  8. global a
  9. a +=1
  10. a*10/2-3
  11. if __name__ == '__main__':
  12. start = time.time()
  13. ps = []
  14. for i in range(3):
  15. p = Process(target=task)
  16. p.start()
  17. ps.append(p)
  18. for p in ps:
  19. p.join()
  20. print(time.time()-start)
  21. 结果:5.455920934677124
  22.  
  23. #多线程解决计算密集型
  24. from threading import Thread
  25. import time
  26. a = 10
  27. def task():
  28. for i in range(10000000):
  29. global a
  30. a +=1
  31. a*10/2-3
  32. if __name__ == '__main__':
  33. start = time.time()
  34. ts = []
  35. for i in range(3):
  36. t = Thread(target=task)
  37. t.start()
  38. ts.append(t)
  39. for t in ts:
  40. t.join()
  41. print(time.time()-start)
  42. 结果:8.375339031219482
  43.  
  44. #多进程解决IO密集型
  45. from multiprocessing import Process
  46. import time
  47. def task():
  48. path =r'E:\python试学视频\day27、28选课系统\11 测试程序2.mp4'
  49. with open(path,mode='rb') as f:
  50. while True:
  51. data = f.read(1024)
  52. if not data:
  53. break
  54. if __name__ == '__main__':
  55. start = time.time()
  56. ps = []
  57. for i in range(3):
  58. p = Process(target=task)
  59. p.start()
  60. ps.append(p)
  61. for p in ps:
  62. p.join()
  63. print(time.time()-start)
  64. 结果:0.3124856948852539
  65. #多线程解决IO密集型
  66. from threading import Thread
  67. import time
  68. a = 10
  69. def task():
  70. path =r'E:\python试学视频\day27、28选课系统\11 测试程序2.mp4'
  71. with open(path,mode='rb') as f:
  72. while True:
  73. data = f.read(1024)
  74. if not data:
  75. break
  76. if __name__ == '__main__':
  77. start = time.time()
  78. ts = []
  79. for i in range(3):
  80. t = Thread(target=task)
  81. t.start()
  82. ts.append(t)
  83. for t in ts:
  84. t.join()
  85. print(time.time()-start)
  86. 结果:0.1250016689300537

  

二.GIL锁与自定义锁的区别

GIL是用于保护解释器相关的数据,解释器也是一段程序,肯定有其定义的各种数据

GIL并不能保证自己定义的数据的安全,所以当程序中出现多线程共享数据的时候就需要自定义加锁.

三.线程池与进程池

什么是进程池/线程池?

池表示是一个容器,本质就是一个存储进程或线程的列表

IO密集型使用线程池,计算密集型使用进程池

为什么需要线程池/进程池?

很多情况下需要控制进程或者线程在一个合理的范围内,线程/进程池不仅帮我们控制线程/进程的数量,还帮我们完成了线程/进程的创建,销毁,以及任务的分配

线程池的使用:

  1. from concurrent.futures import ThreadPoolExecutor
  2. from threading import current_thread,active_count
  3. import time
  4. #创建线程池,指定最大线程数为3 如果不指定 默认为cpu核心数*5
  5. pool = ThreadPoolExecutor(3) #不会立即开启子线程
  6. def task():
  7. print('%s running..'%current_thread().name)
  8. print(active_count())
  9. time.sleep(2)
  10. #提交任务到线程池
  11. for i in range(10):
  12. pool.submit(task)

进程池的使用:

  1. from concurrent.futures import ProcessPoolExecutor
  2. import time,os
  3. #创建进程池,最大进程数为3,默认为cpu个数
  4. pool = ProcessPoolExecutor(3)#不会立即开启子进程
  5. def task():
  6. print('%s running..'%os.getpid())
  7. time.sleep(2)
  8. if __name__ == '__main__':
  9. #提交任务到进程池
  10. for i in range(10):
  11. pool.submit(task)
  12. #第一次提交任务时会创建进程后续提交任务直接交给已经存在的进程来完成,如果没有空闲进程就等待
  13. 结果:
  14. 1464 running..
  15. 11732 running..
  16. 8236 running..
  17.  
  18. 1464 running..
  19. 11732 running..
  20. 8236 running..
  21.  
  22. 1464 running..
  23. 11732 running..
  24. 8236 running..
  25.  
  26. 1464 running..

案例:TCP中的应用

首先要明确,TCP是IO密集型,应该使用线程池

  1. #多线程TCP服务器
  2. from concurrent.futures import ThreadPoolExecutor
  3. import socket
  4. server = socket.socket()
  5. server.bind(('192.168.12.207',4396))
  6. server.listen()
  7. pool = ThreadPoolExecutor(3) #线程池,控制可以连接到服务器的客户端的个数
  8. def task(client):
  9. while True:
  10. try:
  11. data = client.recv(1024)
  12. if not data:
  13. client.close()
  14. break
  15. client.send(data.upper())
  16. except ConnectionResetError:
  17. client.close()
  18. break
  19. while True:
  20. client,addr = server.accept()
  21. t = pool.submit(task,client) 
  1. #多线程TCP客户端
  2. #使用多线程是为了可以一直输入,不用等输出了才可以输入
  3. from threading import Thread
  4. import socket
  5. client = socket.socket()
  6. client.connect(('192.168.12.207',4396))
  7. def send_msg():
  8. while True:
  9. msg = input('>>:').strip()
  10. if not msg:
  11. continue
  12. client.send(msg.encode('utf-8'))
  13. send_t = Thread(target=send_msg)
  14. send_t.start()
  15. while True:
  16. try: #这个也要自定义抛出异常,如果服务器终止,客户端也会报错
  17. data = client.recv(1024)
  18. print(data.decode('utf-8'))
  19. except:
  20. client.close()
  21. break

 

与信号量的区别:

信号量也是一种锁,适用于保证同一时间能有多少个进程或线程访问

而线程池和进程池,没有对数据访问进行限制仅仅是控制数量

四.同步与异步

同步(调用/执行/任务/提交),发起任务后必须等待任务结束,拿到一个结果才能继续运行

异步 发起任务后不需要关系任务的执行过程,可以继续往下运行,但还是需要结果

异步效率高于同步但是并不是所有任务都可以异步执行,判断一个任务是否可以异步的条件是,任务发起方是否立即需要执行结果

同步不等于阻塞 异步不等于非阻塞当使用异步方式发起任务时 任务中可能包含io操作 异步也可能阻塞同步提交任务 也会卡主程序 但是不等同阻塞,因为任务中可能在做一些计算任务,CPU没有切换到其他程序

  1. from concurrent.futures import ThreadPoolExecutor
  2. import time
  3. pool = ThreadPoolExecutor()
  4. def task():
  5. time.sleep(1)
  6. print('sub thread run...')
  7. for i in range(10):
  8. pool.submit(task) #submit是以异步的方式提交任务
  9. print('over')
  1. from concurrent.futures import ThreadPoolExecutor
  2. import time
  3. pool = ThreadPoolExecutor()
  4. def task(i):
  5. time.sleep(1)
  6. print('sub thread run ...')
  7. i += 1
  8. return i
  9. for i in range(10):
  10. f = pool.submit(task,i)
  11. print(f)
  12. print(f.result()) #result是阻塞的,会等到这个任务执行完毕才能继续执行,会将异步变为同步
  13. print('over')
  1. #同步又变为了异步
  2. from concurrent.futures import ThreadPoolExecutor
  3. import time
  4. pool = ThreadPoolExecutor()
  5. def task(i):
  6. time.sleep(1)
  7. print('sub thread run ...')
  8. i += 1
  9. return i
  10. fs = []
  11. for i in range(10):
  12. f = pool.submit(task,i)
  13. fs.append(f)
  14. #是一个阻塞函数,会等到池子中的所有任务完成后继续执行
  15. pool.shutdown() #里面有一个wait参数:默认值是True
  16. #注意:shutdown之后就不能提交新任务了
  17. for i in fs:
  18. print(i.result())
  19. print('over')

  

CIL锁,GIL与线程池的区别,进程池和线程池,同步与异步的更多相关文章

  1. Java进程和线程关系及区别

    1.定义 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基 ...

  2. python笔记-10(socket提升、paramiko、线程、进程、协程、同步IO、异步IO)

    一.socket提升 1.熟悉socket.socket()中的省略部分 socket.socket(AF.INET,socket.SOCK_STREAM) 2.send与recv发送大文件时对于黏包 ...

  3. Python之路(第四十一篇)线程概念、线程背景、线程特点、threading模块、开启线程的方式

    一.线程 ​ 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令的集合,它是 ...

  4. java中的线程问题(一)什么是线程。

    线程--什么是进程 进程--概念 要解释线程,就必须明白什么是进程. 什么是进程呢? 进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进 ...

  5. 并发编程: GIL锁、GIL与互斥锁区别、进程池与线程池的区别

    一.GIL 二.关于GIL性能的讨论 三.计算密集测试 四.IO密集测试 五.GIL与互斥锁 六.TCP客户端 七.进程池 八.进程什么时候算是空闲 九.线程池 一.GIL GIL Global In ...

  6. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  7. python 线程队列、线程池、全局解释器锁GIL

    一.线程队列 队列特性:取一个值少一个,只能取一次,没有值的时候会阻塞,队列满了,也会阻塞 queue队列 :使用import queue,用法与进程Queue一样 queue is especial ...

  8. GIL全局解释器锁+GIL全局解释器锁vs互斥锁+定时器+线程queue+进程池与线程池(同步与异步)

    以多线程为例写个互斥锁 from threading import Thread ,Lockimport timemutex = Lock() n = 100 def task(): global n ...

  9. Python进阶----GIL锁,验证Cpython效率(单核,多核(计算密集型,IO密集型)),线程池,进程池

    day35 一丶GIL锁 什么是GIL锁:    存在Cpython解释器,全名:全局解释器锁.(解释器级别的锁) ​   GIL是一把互斥锁,将并发运行变成串行. ​   在同一个进程下开启的多个线 ...

随机推荐

  1. 巧用CurrentThread.Name来统一标识日志记录(完结篇)

    ▄︻┻┳═一Agenda: ▄︻┻┳═一巧用CurrentThread.Name来统一标识日志记录 ▄︻┻┳═一巧用CurrentThread.Name来统一标识日志记录(续) ▄︻┻┳═一巧用Cur ...

  2. selenium获取文本

    # 标题list_title = driver.find_elements_by_xpath('//*[@id="share-content"]/div/div[1]/ul/li/ ...

  3. sql server行列转化和行列置换

    行列转换: 姓名 课程 分数 张三 语文 74 张三 数学 83 张三 物理 93 李四 语文 74 李四 数学 84 李四 物理 94 想变成(得到如下结果): 姓名 语文 数学 物理 ---- - ...

  4. [10]Windows内核情景分析---中断处理

    中断处理 每个cpu有一张中断表,简称IDT. IDT的整体布局:[异常->空白->5系->硬](推荐采用7字口诀的方式重点记忆) 异常:前20个表项存放着各个异常的描述符(IDT表 ...

  5. Web API 跨域问题

    解决办法: 1.web.config <system.webServer> <handlers> <remove name="ExtensionlessUrlH ...

  6. yum 与 apt 的对比

    一.概念 使用yum/apt之前,你很可能会遇到配置源(ubuntu下一般内置的就比较好,所以可能很少人手动配置),那这个源是什么呢,就是告诉apt/yum,安装软件的时候你要从哪里下载.比如你使用1 ...

  7. Java多线程-----理解CountDownLatch

       CountDownLatch简介  CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier.Semaphore.ConcurrentHa ...

  8. django中orm使用的注意事项

    必备小知识点 <1> all(): 查询所有结果 <2> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者 ...

  9. c# 定义委托和使用委托(事件的使用)

    使用委托时要先实例化,和类一样,使用new关键字产生委托的新实例,然后将一个或者多个与委托签名匹配的方法与委托实例关联.随后调用委托时,就会调用所有与委托实例关联的方法. 与委托关联可以是任何类或者结 ...

  10. Subversion1.8源码安装流程

    为了解决svnamin:Unrecognized record type in stream的问题,决定将Subversion1.7升级为Subversion1.8 Subversion1.8的源码安 ...