文章目录

  • 消息队列
  • IPC 机制(进程间通信)
  • 生产者消费者模型
    • 生产者与消费者是什么模型
    • 模型的三要素
    • 通过代码来实现一下生产者消费者模型
  • 什么是线程
  • 线程实现TCP服务端开发
  • 线程join方法
  • 线程间数据共享
  • 线程对象属性和方法
  • 守护线程
  • GIL全局解释器锁

消息队列

队列:先进先出

堆栈:先进后出

from multiprocessing import Queueq = Queue(5)  # 自定义队列的长度
# 朝队列中存放数据
q.put(111)
q.put(222)
q.put(333)
print(q.full())  # False  判断队列是否满了
q.put(444)
q.put(555)
print(q.full())  # True
# q.put(666)  # 超出最大长度 原地阻塞等待队列中出现空位
print(q.get())
print(q.get())
print(q.empty())  # False  判断队列是否空了
print(q.get())
print(q.get())
print(q.get())
print(q.empty())  # True
# print(q.get())  # 队列中没有值 继续获取则阻塞等待队列中给值
print(q.get_nowait())  # 队列中如果没有值 直接报错
"""full()empty()get_nowait()
上述方法能否在并发的场景下精准使用???不能用!!!之所以介绍队列是因为它可以支持进程间数据通信
"""

IPC 机制(进程间通信)

1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互

from multiprocessing import Process, Queuedef producer(q):# print('子进程producer从队列中取值>>>:', q.get())q.put('子进程producer往队列中添加值')def consumer(q):print('子进程consumer从队列中取值>>>:', q.get())if __name__ == '__main__':q = Queue()p = Process(target=producer, args=(q, ))p1 = Process(target=consumer, args=(q,))p.start()p1.start()# q.put(123)  # 主进程往队列中存放数据123print('主进程')

生产者消费者模型

生产者与消费者是什么模型

生产者
负责生产/制作数据

消费者

负责消费/处理数据

生产者和消费者彼此之间不直接通讯,而是通过队列来进行通讯,生产者生产完的数据不用等待消费者处理,直接放到队列中,消费者不找消费要数据,而是直接从队列中取,队列就相当于一个缓冲区,平衡生产者和消费者的处理能力。

模型的三要素

1.生产者
2.消费者
3.队列(缓冲区)

通过代码来实现一下生产者消费者模型

from queue import Queue
import threading
import time
#创建队列
q=Queue(10)
# 定义一个生产者
def producer(name):#生产计数count=1while True:q.join()q.put(count)print("生产者%s正在生产第%d个产品"%(name,count))count+=1time.sleep(1)#定义一个消费者
def customer(name):count=1while True:hao_zi=q.get()print("消费者%s正在消耗第%d个商品"%(name,hao_zi))count+=1q.task_done()if __name__ == '__main__':t1=threading.Thread(target=producer,args=("A", ))t2=threading.Thread(target=customer,args=("a", ))t1.start()t2.start()

我们加上 time.sleep()这样可以清晰的看到生产者生产一个通过put()往队列中放一个,消费者消费一个通过get()取出一个 ,当当生产者生产完,消费者通过get()取空之后,就一直在原地等待。

from queue import Queue
import threading
import time
#创建队列
q=Queue(10)
# 定义一个生产者
def producer(name):#生产计数count=1while True:q.join()q.put(count)print("生产者%s正在生产第%d个产品"%(name,count))count+=1#定义一个消费者
def customer(name):count=1while True:hao_zi=q.get()print("消费者%s正在消耗第%d个商品"%(name,hao_zi))count+=1q.task_done()time.sleep(1)if __name__ == '__main__':t1=threading.Thread(target=producer,args=("A", ))t2=threading.Thread(target=customer,args=("a", ))t1.start()t2.start()

当生产者生产完通过put()放在队列中就等着消费者通过get()取出

生产者与消费者模型通过队列平衡生产力与消费力,就是生产者一直不停的生产,消费者可以不停的消费,生产者与消费者不直接沟通。

线程理论

什么是线程

 """
进程:资源单位(一个独立的内存空间)
线程:执行单位将操作系统比喻成一个大的工厂
那么进程就相当于工厂里面的车间
而线程就是车间里面的流水线每一个进程肯定自带一个线程再次总结:
进程:资源单位(起一个进程仅仅只是在内存空间中开辟一块独立的空间)线程:执行单位(真正被cpu执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要)运行一个进程意味着相当于进程里面的线程再被cpu运行 ( 代码相当于线程 代码所在的空间叫进程 代码运行(干活的人)叫线程 进程和线程都是虚拟单位,只是为了我们更加方便的描述问题2 为何要有线程 开设进程
1.申请内存空间    耗资源
2.“拷贝代码”   耗资源
开线程
一个进程内可以开设多个线程,在用一个进程内开设多个线程无需再次申请内存空间操作总结:
开设线程的开销要远远的小于进程的开销
同一个进程下的多个线程数据是共享的!!! ( 一个车间里面东西能共用)

开设线程的两种方式

第一种:

from threading import Thread
import timedef task(name):print('%s is running' % name)time.sleep(0.5)print('%s is over' % name)# 开启线程不需要在main下面执行,直接书写就可以
# 但我们还是习惯性的写在main的下面t = Thread(target=task,args=('juson',))
t.start()print('主')  # 注意: 会发现运行的结果先运行了task里面的程序和进程不一样(先运行下面的主 因为子进程需要创建内存)# 线程而是在进程内存里面开创的(所以创建线程开销非常小 几乎是代码一执行start线程就已经创建了)

第二种:

from threading import Thread
import timeclass MyThread(Thread):def __init__(self,name): # 重写了父类thread的方法又不知道别人的方法里有啥 你就调用父类的方法super().__init__()self.name = namedef run(self):print('%s is running ' % self.name)time.sleep(1)print('%s DSB' % self.name)if __name__ == '__main__':t = MyThread('juson')t.start()print('主')

线程实现TCP服务端开发

import socket
from threading import Thread
from multiprocessing import Process
"""
服务端1.要有固定的IP和PORT2.24小时不间断提供服务3.能够支持并发"""# 将服务的代码单独封装成一个函数
def talk(conn):# 通信循环while True:try:data = conn.recv(1024)# 针对mac linux 客户端断开链接后if len(data) == 0: breakprint(data.decode('utf-8'))conn.send(data.upper())except ConnectionResetError as e:print(e)breakconn.close()server =socket.socket()  # 括号内不加参数默认就是TCP协议
server.bind(('127.0.0.1',8080))
server.listen(5)# 链接循环
while True:conn, addr = server.accept()  # 接客# 叫其他人来服务客户# t = Thread(target=talk,args=(conn,))t = Process(target=talk,args=(conn,))t.start()

客户端:

import socketclient = socket.socket()
client.connect(('127.0.0.1',8080))while True:client.send(b'hello world')data = client.recv(1024)print(data.decode('utf-8'))

线程join方法

from threading import Thread
import timedef task(name):print('%s is running'%name)time.sleep(3)print('%s is over'%name)if __name__ == '__main__':t = Thread(target=task,args=('jason',))t.start()t.join()  # 主线程等待子线程运行结束再执行print('主')

线程间数据共享

from threading import Thread
import timemoney = 100def task():global moneymoney = 666print(money)if __name__ == '__main__':t = Thread(target=task)t.start()t.join()print(money)

线程对象属性和方法

1.验证一个进程下的多个线程是否真的处于一个进程
验证确实如此
2.统计进程下活跃的线程数
active_count() # 注意主线程也算!!!
3.获取线程的名字
1.current_thread().name
MainThread 主线程
Thread-1、Thread-2 子线程
2.self.name

守护线程

# from threading import Thread
# import time
#
#
# def task(name):
#     print('%s is running'%name)
#     time.sleep(1)
#     print('%s is over'%name)
#
#
# if __name__ == '__main__':
#     t = Thread(target=task,args=('json',))
#     t.daemon = True
#     t.start()
#     print('主')"""
主线程运行结束之后不会立刻结束 会等待所有其他非守护线程结束才会结束(也就是比主线程慢还在运行的子线程)因为主线程的结束意味着所在的进程的结束
"""

GIL全局解释器锁

一、GIL 全局解释器锁

定义:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)

翻译结果:

1、GIL 不是 Python 的特点,而是 CPython 解释器的特点;

2、在 CPython 解释器中,GIL 是一把互斥锁,用来阻止同一个进程下多个线程的同时执行

3、因为 CPython 解释器的内存管理并不安全( 内存管理—垃圾回收机制)

稍加解释:在没有 GIL 锁的情况下,有可能多线程在执行一个代码的同时,垃圾回收机制线程对所执行代码的变量直接回收,导致运行报错;

重点:

1、GIL 不是 Python 的特点,而是 CPython 解释器的特点;

2、GIL 锁是加在 CPython 解释器上的,是保证解释器级别的数据的安全;

3、GIL 锁会导致同一个进程下多个线程的不能同时执行

4、不同的数据除了 GIL 锁,还需要一把互斥锁,来保证数据处理不会错乱
注意:GIL 锁是加在 CPython 解释器上的,进程先获取 GIL 锁,在获取 CPython 解释器

二、为什么会有 GIL 锁?
Python 是一门解释型的语言,这就意味着代码是解释一行,运行一行,它并不清楚代码全局;

因此,每个线程在调用 cpython 解释器 在运行之前,需要先抢到 GIL 锁,然后才能运行。

编译型的语言就不会存在 GIL 锁,编译型的语言会直接编译所有代码,就不会出现这种问题。

三、GIL 锁与普通锁的区别

启动 10 个线程 去修改同一个变量–number

方案一:在线程运行的函数中加入 time.sleep(0.1),而且没有加入数据的互斥锁– 结果为 9 而不是 0,运行时间为 0.1 秒左右

import time
from threading import Thread,Lock# mutex = Lock()
number = 10def func():global numbertem = numbertime.sleep(0.1)  number = tem -1if __name__ == '__main__':thread_list = []for i in range(10):thread = Thread(target=func)thread.start()thread_list.append(thread)for i in thread_list:i.join()print(number)

加上 time.sleep(0.1),会让所有的线程将获得的 GIL 锁运行到这一行代码直接释放掉,其他的线程就能获取到 GIL 锁

运行的步骤为:10 个线程会竞争 GIL 锁,然后运行代码,运行到 time.sleep(0.1),释放 GIL 锁,在等待期间,其他线程获取到 GIL 锁运行代码,再释放GIL 锁… 直到第10 个线程释放 GIL 锁之后,此时10 个进程都已经获取到了 tem = 10。

然后,第一个线程再获取 GIL 锁运行代码,修改 number = 10 - 1 = 9 , 修改结束,第一个线程运行结束,释放 GIL 锁;

其次,第二个线程再获取 GIL 锁运行代码,修改 number = 10 - 1 = 9 , 修改结束,第一个线程运行结束,释放 GIL 锁;

直到全部线程结束,那么结果就是 9 而不是 0

方案二:在线程运行的函数中 删除 time.sleep(0.1),而且没有加入数据的互斥锁– 结果为 0,运行时间为 0.1 秒左右

import time
from threading import Thread,Lock# mutex = Lock()
number = 10def func():global numbertem = number# time.sleep(0.1)  number = tem -1if __name__ == '__main__':thread_list = []for i in range(10):thread = Thread(target=func)thread.start()thread_list.append(thread)for i in thread_list:i.join()print(number)

那么,为什么注释了一行代码,结果却有差距呢?

因为 time.sleep(0.1) 所代表的的操作是 IO 操作,当线程运行到这一行代码的时候,要进行 IO 操作,需要释放 CPU 资源,也就是说,线程这个时候需要等待 IO操作结束,此时线程就会处于"阻塞态"可以简单理解为这个线程什么活都不干了,就等着 IO结束,那么这个线程不干活了,其他线程要干活啊,所以,其他的线程就会获取 GIL 锁,再运行代码。

如果没有 time.sleep(0.1) 所代表的的操作是 IO 操作,线程就会一直运行到结束,才释放 GIL 锁,其他的线程才能获取 GIL 锁,运行代码;

那么,反映在这个例子中,第一个线程获取 GIL 锁以后,会直接运行到最后一步,修改了 number = tem -1=10-1=9;

下一个线程,再获取到的 number 就是 9 了,一次类推,结果为 0.

方案三:在方案一的基础上再加入数据的互斥锁– 结果为 0, 但是运行时间为 1 秒

import time
from threading import Thread,Lockmutex = Lock()
number = 10def func(mutex):mutex.acquire()global numbertem = numbertime.sleep(0.1)number = tem -1mutex.release()if __name__ == '__main__':thread_list = []for i in range(10):thread = Thread(target=func,args=(mutex,))thread.start()thread_list.append(thread)for i in thread_list:i.join()print(number)

运行结果的区别在于,第一个线程在运行时,先获取 GIL 锁,然后对 number数据进行上锁,运行到 time.sleep(0.1) 线程变成"阻塞态",释放 GIL 锁;

第二个进程获取 GIL 锁,但是由于 number数据已经被上锁了,无法操作,只能变成"阻塞态",释放 GIL 锁;

其他线程也是一样,当第一个线程 time.sleep(0.1) 运行结束之后(IO 操作结束) ,第一个线程会变成"就绪态",那么就可以获得 GIL 锁,继续运行,直到最终修好 number ,释放掉互斥锁,线程运行结束,然后释放 GIL 锁;

也就是说,当第一个进程运行 time.sleep(0.1) 的时候,其他的线程什么都做不了,只能干等;

当 数据的互斥锁被释放之后,其他的线程就可以获取互斥锁,那么其他的线程都会变成"就绪态",但是只有一个线程能够获取 (服从 CPU 的调度算法,这里不进行拓展了),能够获取互斥锁的线程会对 GIL 锁,互斥锁进行上锁,再运行… …

所以最终的结果是 0,但是运行时间是 每个线程的运行时间相加。
3.1 为什么要再加上数据锁?

就方案二,与方案三而言,明显只有 GIL 锁的运行时间更短,为什么要有方案三呢?

其实,我们在编程时,要考虑到数据传输的延迟问题,很明显,现阶段的计算机的数据传输不可能做到无延迟,那么就必须对数据单独加锁,否则就会出现数据错乱。

四、多线程无法利用多核优势?

由于 GIL 的存在,即使是多个线程处理任务,但是最终也只有一个线程在工作,那么是不是多线程真的一点用处都没有呢?

对于需要执行的任务来说,分为两种:计算密集型、IO 密集型

假如一个 计算密集型 的任务需要 10s 的执行时间,总共有 4 个这样的任务

在 4核及以上 的情况下:

多进程:需要开启 4 个进程,但是 4 个 CPU 并行,最终只需要消耗 10s 多一点的时间

多线程:只需要开1 个进程,这个进程开启 4 个线程,开启线程所消耗的资源很少,但是由于最终执行是只有一个 CPU 可以工作,所以最终消耗 40s 多的时间

假如是多个 IO密集型 的任务–CPU 大多数时间是处于闲置状态,频繁的切换

多进程:进程进行切换需要消耗大量资源

多线程:线程进行切换并不需要消耗大量资源

【python线程与GIL 锁】相关推荐

  1. Python中的GIL锁

    Python中的GIL锁 在Python中,可以通过多进程.多线程和多协程来实现多任务. 在多线程的实现过程中,为了避免出现资源竞争问题,可以使用互斥锁来使线程同步(按顺序)执行. 但是,其实Pyth ...

  2. 彻底弄懂Python中的GIL锁

    彻底弄懂Python中的GIL锁 转载:https://blog.csdn.net/yushuaigee/article/details/86537474 刚学习python时,我关注了许多介绍pyt ...

  3. 同步锁 php,python线程中同步锁详解

    这篇文章主要为大家详细介绍了python线程中同步锁的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十 ...

  4. Python 线程----线程方法,线程事件,线程队列,线程池,GIL锁,协程,Greenlet

    主要内容: 线程的一些其他方法 线程事件 线程队列 线程池 GIL锁 协程 Greenlet Gevent 一. 线程(threading)的一些其他方法 from threading import ...

  5. python 线程, GIL 和 ctypes

    1 GIL 与 Python 线程的纠葛 GIL 是什么东西?它对我们的 python 程序会产生什么样的影响?我们先来看一个问题, 运行下面这段 python 程序,CPU 占用率会到多少: [py ...

  6. Python 并发编程(三):谈谈 Python 线程中的“锁机制”

    1. 什么是锁? 在开发中,锁 可以理解为通行证. 当你对一段逻辑代码加锁时,意味着在同一时间有且仅能有一个线程在执行这段代码. 在 Python 中的锁可以分为两种: 互斥锁 可重入锁 2. 互斥锁 ...

  7. python线程和GIL

    GIL 与 Python 线程的纠葛 GIL 是什么?它对 python 程序会产生怎样的影响?我们先来看一个问题.运行下面这段 python 代码,CPU 占用率是多少? # 请勿在工作中模仿,危险 ...

  8. python线程的互斥锁

    当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁. 互斥锁为资源引入一个状态:锁定/非锁定 某个线程要更改共享数据时 ...

  9. python为什么有gil锁_为什么目前python3的全局锁gil性能远逊于python2

    {"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],&q ...

最新文章

  1. python冒泡排序与常用数学计算
  2. 加密解密、食谱、新冠序列,各种有趣的开源项目Github上都有
  3. 组织应该采用集中式发电机吗?
  4. php array in array,浅谈PHP array_search 和 in_array 函数效率问题
  5. java对象赋值_Java 对象不使用时为什么要赋值为 null?
  6. 分布式消息系统Kafka初步
  7. Chapter 1. Introduce
  8. 小米Android版本不不一致,小米5s卡刷包android版本不一致怎么解决
  9. python 按比例缩小图片
  10. mysql.sock介绍
  11. Android中的保活机制
  12. 台湾ICPlus九旸 5接口FE以太网交换机 IP175G,IP175GH/GHI
  13. 引入思考的电影电视动漫(二)
  14. matlab在频率特性法中的应用实验目的,matlab软件实习报告
  15. App通过(后台返回apk链接)下载apk并且安装
  16. 分享到新浪微博/QQ空间/开心网/人人网/豆瓣网/QQ书签/百度搜藏/美味书签 代码...
  17. 英语语法汇总(6.副词)
  18. 2021-10-23 python第一天
  19. 思考┊读大学,究竟读什么?【狐狸强烈推荐的一本书】
  20. #布鞋院士#李小文的牛掰论文《定量遥感的发展与创新》

热门文章

  1. 人工智能 -- 模拟退火算法解决TSP问题(JAVA版)
  2. Unity 3D 制作贴纸,模型任意形状贴画制作,简易贴花工具,撞击模型凹凸效果
  3. 循序渐进:用python做金融量化分析(三)如何寻找均线系统的最优参数
  4. 盗贼之海-游戏概念艺术
  5. 单行函数,聚合函数课后练习
  6. AirPods3丢失怎么找
  7. [译] 为什么我们渴求女性来设计 AI
  8. Android 获取手机的 IMEI 值
  9. Win10 系统,LOL游戏中切屏时黑屏时间过长的解决方法
  10. JNPF旗舰版源码,JNPF3.3 3.4.1 快速开发框架源码部署文档入门说明