线程

一.什么是线程?

操作系统原理相关的书,基本都会提到一句很经典的话: "进程是资源分配的最小单位,线程则是CPU调度的最小单位"。

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

好处 :

1.易于调度。
2.提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
3.开销少。创建线程比创建进程要快,所需开销很少。
4.利于充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。

在解释python多线程的时候. 先和大家分享一下 python 的GIL 机制。

二.GIL(Global Interpreter Lock)全局解释器锁

Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python 虚拟机按以下方式执行:

1 设置GIL

2 切换到一个线程去运行

3 运行:
    a. 指定数量的字节码指令,或者
    b. 线程主动让出控制(可以调用time.sleep(0))

4 把线程设置为睡眠状态

5 解锁GIL

6 再次重复以上所有步骤

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。Python同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

还有,就是在做I/O操作时,GIL总是会被释放。对所有面向I/O 的(会调用内建的操作系统C 代码的)程序来说,GIL 会在这个I/O 调用之前被释放,以允许其它的线程在这个线程等待I/O 的时候运行。如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过 sys.setcheckinterval 来调整)如果某线程并未使用很多I/O 操作,它会在自己的时间片内一直占用处理器(和GIL)。也就是说,I/O 密集型的Python 程序比计算密集型的程序更能充分利用多线程环境的好处。

三.线程的生命周期

各个状态说明:

  • New新建 :新创建的线程经过初始化后,进入Runnable状态。

  • Runnable就绪:等待线程调度。调度后进入运行状态。

  • Running运行:线程正常运行

  • Blocked阻塞:暂停运行,解除阻塞后进入Runnable状态重新等待调度。

  • Dead消亡:线程方法执行完毕返回或者异常终止。

可能有3种情况从Running进入Blocked:

  • 同步:线程中获取同步锁,但是资源已经被其他线程锁定时,进入Locked状态,直到该资源可获取(获取的顺序由Lock队列控制)

  • 睡眠:线程运行sleep()或join()方法后,线程进入Sleeping状态。区别在于sleep等待固定的时间,而join是等待子线程执行完。sleep()确保先运行其他线程中的方法。当然join也可以指定一个“超时时间”。从语义上来说,如果两个线程a,b, 在a中调用b.join(),相当于合并(join)成一个线程。将会使主调线程(即a)堵塞(暂停运行, 不占用CPU资源), 直到被调用线程运行结束或超时, 参数timeout是一个数值类型,表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程结束。最常见的情况是在主线程中join所有的子线程。

  • 等待:线程中执行wait()方法后,线程进入Waiting状态,等待其他线程的通知(notify)。wait方法释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时(如果提供了timeout参数的话)。当线程被唤醒并重新占有琐的时候,程序才会继续执行下去。

threading.Lock()不允许同一线程多次acquire(), 而RLock允许, 即多次出现acquire和release

四.Python threading模块

上面介绍了这么多理论.下面我们用python提供的threading模块来实现一个多线程的程序

threading 提供了两种调用方式:

  • 直接调用

import threading

def func(n): # 定义每个线程要运行的函数
   while n > 0:
       print("当前线程数:", threading.activeCount())
       n -= 1

for x in range(5):
   t = threading.Thread(target=func, args=(2,))  # 生成一个线程实例,生成实例后 并不会启动,需要使用start命令
   t.start() #启动线程

  • 继承式调用

class MyThread(threading.Thread): # 继承threading的Thread类
   def __init__(self, num):
       threading.Thread.__init__(self) # 必须执行父类的构造方法
       self.num = num # 传入参数 num

def run(self):  # 定义每个线程要运行的函数
       while self.num > 0:
           print("当前线程数:", threading.activeCount())
           self.num -= 1

for x in range(5):
   t = MyThread(2) # 生成实例,传入参数
   t.start() #启动线程

两种方式都可以调用我们的多线程方法。

五.子线程阻塞

运行下面的代码,看看结果.

import threading
def func(n):
   while n > 0:
       print("当前线程数:", threading.activeCount())
       n -= 1
for x in range(5):
   t = threading.Thread(target=func, args=(2,))
   t.start()

print("主线程:", threading.current_thread().name)

运行结果:

当前线程数: 2
当前线程数: 2
当前线程数: 2
当前线程数: 2
当前线程数: 2
当前线程数: 3
当前线程数: 3
当前线程数: 3
主线程: MainThread
当前线程数: 3
当前线程数: 3

那我们如何阻塞子线程让他们运行完,在继续后面的操作呢.这个时候join()方法就派上用途了. 我们改写代码:

import threading

def func(n):
   while n > 0:
       print("当前线程数:", threading.activeCount())
       n -= 1

threads = [] #运行的线程列表
for x in range(5):
   t = threading.Thread(target=func, args=(2,))
   threads.append(t) # 将子线程追加到列表
   t.start()

for t in threads:
   t.join()

print("主线程:", threading.current_thread().name)

join的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束,如果结束则跳转执行下一个线程的join函数。

先看看这个:

  1. 阻塞主进程,专注于执行多线程中的程序。

  2. 多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。

  3. 无参数,则等待到该线程结束,才开始执行下一个线程的join。

  4. 参数timeout为线程的阻塞时间,如 timeout=2 就是罩着这个线程2s 以后,就不管他了,继续执行下面的代码。

六.线程锁(互斥锁)

一个进程可以开启多个线程,那么多么多个进程操作相同数据,势必会出现冲突.那如何避免这种问题呢?

import threading,time

num = 10 #共享变量

def func():
   global num
   lock.acquire() # 加锁
   num = num - 1
   lock.release() # 解锁
   print(num)

threads = []
lock = threading.Lock() #生成全局锁
for x in range(10):
   t = threading.Thread(target=func)
   threads.append(t)
   t.start()

for t in threads:
   t.join()

通过 threading.Lock() 我们可以申请一个锁。然后 acquire 方法进入临界区.操作完共享数据 使用 release 方法退出.

临界区的概念: 百度百科

在这里补充一下:Python的Queue模块是线程安全的.可以不对它加锁操作.

聪明的同学 会发现一个问题? 咱们不是有 GIL 吗 为什么还要加锁?

这个问题问的好!我们下一节,将对这个问题进行探讨.

七.LOCK 和 GIL

GIL的锁是对于一个解释器,只能有一个thread在执行bytecode。所以每时每刻只有一条bytecode在被执行一个thread。GIL保证了bytecode 这层面上是线程是安全的.

但是如果你有个操作一个共享 x += 1,这个操作需要多个bytecodes操作,在执行这个操作的多条bytecodes期间的时候可能中途就换thread了,这样就出现了线程不安全的情况了。

总结:同一时刻CPU上只有单个执行流不代表线程安全。

八.信号量

互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

import threading,time

num = 10

def func():
   global num
   lock.acquire()
   time.sleep(2)
   num = num - 1
   lock.release()
   print(num)

threads = []
lock = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
for x in range(10):
   t = threading.Thread(target=func)
   threads.append(t)
   t.start()

for t in threads:
   t.join()

print("主线程:", threading.current_thread().name)

运行一下上面的代码.你会很明显的发现 每次只执行五个线程。

参考文献

浅谈多进程多线程的选择: http://www.cnblogs.com/zhanht/p/5401685.html

python-多线程(原理篇): https://www.cnblogs.com/chushiyaoyue/p/5818012.html

Python有GIL为什么还需要线程同步?:https://www.zhihu.com/question/23030421

∞∞∞∞∞

IT派 - {技术青年圈}持续关注互联网、区块链、人工智能领域

公众号回复“Python”,

邀你加入{ IT派Python技术群 }

浅谈Python多线程相关推荐

  1. python_浅谈python多线程

    一.什么是线程和进程 这个查了很多资料,最后我理解是这样的 进程:是 CPU 资源分配的最小单位,它主要用来就是资源的分配 线程:是 CPU 调度执行的最小单位,它主要用来系统调度 通俗的说我们电脑任 ...

  2. js打印线程id_浅谈python中的多线程和多进程(二)

    原创:hxj7 本文继续分享一个关于python多线程和多进程区别的例子 前文<浅谈python中的多线程和多进程>中我们分享过一个例子,就是分别利用python中的多线程和多进程来解决高 ...

  3. 获得进程id_浅谈python中的多线程和多进程(二)

    原创:hxj7 本文继续分享一个关于python多线程和多进程区别的例子 前文<浅谈python中的多线程和多进程>中我们分享过一个例子,就是分别利用python中的多线程和多进程来解决高 ...

  4. python中gil锁和线程锁_浅谈Python中的全局锁(GIL)问题

    CPU-bound(计算密集型) 和I/O bound(I/O密集型) 计算密集型任务(CPU-bound) 的特点是要进行大量的计算,占据着主要的任务,消耗CPU资源,一直处于满负荷状态.比如复杂的 ...

  5. python 读excel字符型 数值_浅谈python 读excel数值为浮点型的问题

    浅谈python 读excel数值为浮点型的问题 如下所示: #读入no data = xlrd.open_workbook("no.xlsx") #打开excel table = ...

  6. python中内置的四种数值类型为_浅谈python语言四种数值类型

    Python语言支持四种不同的数值类型,包括int(整数)long(长整数)float(浮点实际值)complex (复数),本文章向码农介绍python 四种数值类型,需要的朋友可以参考一下.希望对 ...

  7. python sys模块作用_浅谈Python中的模块

    模块 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式.在Python中,一个.py文件就称之为一个模块(Mod ...

  8. python中文字符串编码_浅谈python下含中文字符串正则表达式的编码问题

    前言 Python文件默认的编码格式是ascii ,无法识别汉字,因为ascii码中没有中文. 所以py文件中要写中文字符时,一般在开头加 # -*- coding: utf-8 -*- 或者 #co ...

  9. python老是报参数未定义_浅谈Python程序的错误:变量未定义

    Python程序的错误种类 Python程序的错误分两种.一种是语法错误(syntax error).这种错误是语句的书写不符合Python语言的语法规定.第二种是逻辑错误(logic error). ...

最新文章

  1. 解码以色列人工智能产业:正在崛起的竞争者
  2. SQLserver创建与主外键的看法
  3. HDLBits 系列(3)Priority Encoder(Case/Casez)
  4. 合理提升WEB前端性能
  5. 【小白学习keras教程】十一、Keras中文本处理Text preprocessing
  6. python爬取基金历史净值_Python爬取天天基金网历史净值数据
  7. SIEM部署失败的五大原因
  8. Kafka 与Spark的集成
  9. 对象数组根据某属性列的灵活排序
  10. 2.掌握Python基本数据类型,从python陌路人到熟人!
  11. 又到开学季 学习神器走一波 物联网开发板
  12. 基于灰度世界、完美反射、动态阈值等图像自动白平衡算法的原理、实现及效果...
  13. 通信系统仿真中的几个能量归一化问题
  14. Python OrderedDict
  15. HDU1829 A Bug's Life 并查集
  16. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-2.使用Mybatis注解开发视频列表增删改查...
  17. 计算机软件系统包括选择题,青书学堂: (单选题) 计算机软件系统包括 ( )(本题2.0分)...
  18. pyqt5学习笔记——QListView与QListWidget
  19. deepin允许root登录_deepin官网发布深度操作系统15.10
  20. (附源码)springboot音乐播放器小程序 毕业设计 170900

热门文章

  1. Flink的并行度及Slot
  2. 使用python和flask建个人博客---如何修改主页的栏目和图片
  3. java数据类型(最强详解)
  4. java多线程 守护线程setDaemon
  5. 安装 Win10+GTX1050ti+cuda8.0+cuDNN v7.1.4
  6. Oracle 使用Pivot函数 实现行转列碰到的问题
  7. JS 异步编程的 5 种解决方案
  8. # EXCEL 解决页脚文本过长的问题
  9. Redis如何存储对象
  10. ORA-12520错误解决方法