Python线程详解

  • 线程简介
  • 开启多线程
  • 线程之间共享
  • GIL全局解释器锁
  • 线程间通信

线程简介

  • 线程,有时被称为轻量进程(Lightweight Process,LWP),是程序执行流的最小单元。
  • 一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
  • 另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
  • 一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
  • 由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。
  • 就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
  • 每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
  • 线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。
  • 在单个程序中同时运行多个线程完成不同的工作,称为多线程
  • 线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程

为什么要使用多线程?

  • 线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄和其他进程应有的状态。
  • 因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率。
  • 线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
  • 操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能高得要多。
  • 多线程:多线程( 英语: multithreading) ,是指从软件或者硬件上实现多个线程并发执行的技术。
  • 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
  • 具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理( Chip-level multithreading)或同时多线程( Simultaneous multithreading)处理器。
  • 在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多
    于一个线程(台湾译作“执行绪”) ,进而提升整 体处理性能

优点:

  • 进程之间不能共享内存,但线程之间共享内存非常容易。
  • 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。
  • Python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了 Python 的多线程编程。

Python通过两个标准库thread和threading提供对线程的支持。thread提供 了低级别的、原始的线程以及一个简单的锁。

threading模块提供的其他方法:

  • threading. currentThead():返回当前的线程变量。
  • threading enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading. activeCount():返回正在运行的线程数量,与len(threading enumerate())有相同的结果。

线程有5种状态,状态转换的过程如下

开启多线程

import threading
import timedef download(n):images = ['girl.jpg', 'boy.jpg', 'man.jpg']for image in images:print('正在下载。。。。。', image)time.sleep(n)print('下载成功')def listenMusic(n):musics = ['1.music', '2.music', '3.music', '4.music']for music in musics:time.sleep(n)print('正在听{}歌曲'.format(music))
if __name__ == '__main__':t = threading.Thread(target=download, name='aa', args=(1,))t.start()t = threading.Thread(target=listenMusic, name='aa', args=(1,))t.start()

线程之间共享

import threadingmoney = 1000def run1():global moneyfor i in range(100):money -= 1def run2():global moneyfor i in range(100):money -= 1
if __name__ == '__main__':t1 = threading.Thread(target=run1, name='t1')t2 = threading.Thread(target=run1, name='t2')t3 = threading.Thread(target=run1, name='t3')t4 = threading.Thread(target=run1, name='t4')t1.start()t2.start()t3.start()t4.start()t1.join()t2.join()t3.join()t4.join()print('money:', money)

从上图可以看出,四个线程都对变量进行减100的操作,该变量最终止剩下600

GIL全局解释器锁

import threading
from time import sleepn = 0def task1():global nfor i in range(1000000):n += 1print('---->task1中的值是:', n)def task2():global nfor i in range(1000000):n += 1print('---->task2中的值是:', n)
if __name__ == '__main__':t1 = threading.Thread(target=task1, name='t2')t2 = threading.Thread(target=task2, name='t1')t1.start()t2.start()t1.join()t2.join()print('n:', n)

从上图可以看出,如果执行的次数太多,就会被全局解释器锁限制

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

线程与锁的交互如下图所示:

import threading
import random
from time import sleeplock = threading.Lock()
list1 = [0] * 10def task1():lock.acquire()for i in range(len(list1)):list1[i] = 1sleep(0.5)lock.release()def task2():lock.acquire()for i in range(len(list1)):print('---->', list1[i])sleep(0.5)lock.release()
if __name__ == '__main__':t1 = threading.Thread(target=task1, name='t2')t2 = threading.Thread(target=task2, name='t1')t1.start()t2.start()t1.join()t2.join()

从上图可以看出线程t1执行列表

    t2.start()t1.start()t2.join()t1.join()print(list1)

from threading import Thread, Lock
import random
from time import sleeplockA = Lock()
lockB = Lock()
class MyThread(Thread):def run(self):if lockA.acquire():print(self.name + '获取A锁')sleep(0.1)if lockB.acquire(timeout=5):print(self.name + '有获取了B锁,原来还有A锁')lockB.release()lockA.release()class MyThread1(Thread):def run(self):if lockB.acquire():print(self.name + '获取B锁')sleep(0.1)if lockA.acquire(timeout=5):print(self.name + '有获取了A锁,原来还有B锁')lockA.release()lockB.release()
if __name__ == '__main__':t1 = MyThread()t2 = MyThread1()t1.start()t2.start()t1.join()t2.join()

线程间通信

然而还有另外一种尴尬的情况:列表并不是一开始就有的;而是通过线程"create"创建的。如果"set"或者"print" 在"create"还没有运行的时候就访问列表,将会出现一个异常。使用锁可以解决这个问题,但是"set"和"print"将需要一个无限循环——他们不知道"create"什么时候会运行,让"create"在运行后通知"set"和"print"显然是一个更好的解决方案。于是,引入了条件变量。条件变量允许线程比如"set"和"print"在条件不满足的时候(列表为None时)等待,等到条件满足的时候(列表已经创建)发出一个通知,告诉"set" 和"print"条件已经有了,你们该起床干活了;然后"set"和"print"才继续运行。

线程与条件变量的交互如下图所示:


线程运行和阻塞的状态转换

阻塞有三种情况:
同步阻塞是指处于竞争锁定的状态,线程请求锁定时将进入这个状态,一旦成功获得锁定又恢复到运行状态;
等待阻塞是指等待其他线程通知的状态,线程获得条件锁定后,调用“等待”将进入这个状态,一旦其他线程发出通知,线程将进入同步阻塞状态,再次竞争条件锁定;
而其他阻塞是指调用time.sleep()、anotherthread.join()或等待IO时的阻塞,这个状态下线程不会释放已获得的锁定。
import threading
import queue
import random
from time import sleepdef produce(q):i = 0while i < 10:num = random.randint(1, 100)q.put("生产者已产生的数据是%d" % num)print("生产者产生的数据为:%d" % num)sleep(1)i += 1q.put(None)q.task_done()def consume(q):i = 0while True:item = q.get()if item is None:breakprint('消费者获取的数据:', item)sleep(4)q.task_done()
if __name__ == '__main__':q = queue.Queue(10)arr = []t1 = threading.Thread(target=produce, args=(q,))t2 = threading.Thread(target=consume, args=(q,))t1.start()t2.start()t1.join()t2.join()

thread 模块提供的其他方法:

  • thread.interrupt_main(): 在其他线程中终止主线程。
  • thread.get_ident(): 获得一个代表当前线程的魔法数字,常用于从一个字典中获得线程相关的数据。这个数字本身没有任何含义,并且当线程结束后会被新线程复用。
  • thread还提供了一个ThreadLocal类用于管理线程相关的数据,名为 thread._local,threading中引用了这个类。

摘自
Python线程指南
Python 线程详解

参考链接1
参考链接2
参考链接3

Python线程详解相关推荐

  1. python与golang_Golang与python线程详解及简单实例

    Golang与python线程详解及简单实例 在GO中,开启15个线程,每个线程把全局变量遍历增加100000次,因此预测结果是 15*100000=1500000. var sum int var ...

  2. doraemon的python 线程详解

    ### 9.3 线程(开销小)- 线程是进程中的一部分,每一个进程至少有一个线程 - 进程是计算机最小的资源分配单位(进程是负责圈资源) - 线程是计算机中能被CPU调度最小单位(线程是负责执行具体代 ...

  3. python线程详解爬小说_python--多线程爬取顶点小说()

    import requests from lxml import etree from threading import Thread from queue import Queue class My ...

  4. 【python】详解multiprocessing多进程-Pool进程池模块(二)

    [python]详解multiprocessing多进程-process模块(一) [python]详解multiprocessing多进程-Pool进程池模块(二) [python]详解multip ...

  5. python多线程详解 Python 垃圾回收机制

    文章目录 python多线程详解 一.线程介绍 什么是线程 为什么要使用多线程 总结起来,使用多线程编程具有如下几个优点: 二.线程实现 自定义线程 守护线程 主线程等待子线程结束 多线程共享全局变量 ...

  6. Python数据类型详解03

    原文博客地址: Python数据类型详解03 第一篇Python数据类型详解01中主要介绍了Python中的一些常用的数据类型的基础知识 第二篇Python数据类型详解02文章中, 详细介绍了数字(N ...

  7. Python 异常处理 详解

    Python 异常处理 详解 1.错误和异常 1.1 错误 `Error` 1.2 异常 `Exception` 1.3 总结 2.产生异常 3.捕获异常 3.1 语法 3.2 示例 1 3.3 示例 ...

  8. [Python 多线程] 详解daemon属性值None,False,True的区别

    [Python 多线程] 详解daemon属性值None,False,True的区别 记录学习python不懂得和遇到得问题 每个进程至少要有一个线程,并最为程序的入口,这个进程就是主线程. 每个进程 ...

  9. Python|SQL详解之DDL|DML|DQL|DCL|索引|视图、函数和过程|JSON类型|窗口函数|接入MySQL|清屏|正则表达式|executemany|语言基础50课:学习(14)

    文章目录 系列目录 原项目地址 第41课:SQL详解之DDL 建库建表 删除表和修改表 第42课:SQL详解之DML insert操作 delete 操作 update 操作 完整的数据 第43课:S ...

最新文章

  1. Windows版 mysql 5.7.16安装
  2. Centos之压缩和解压缩命令
  3. 外星人进化_深层分析宇宙常数对生命形成进化的影响,外星人或许根本就“不是人”!...
  4. 深入分析glibc内存释放时的死锁bug
  5. 360互联网训练营第十四期——大数据技术开放日
  6. PAT乙级(1015 德才论)
  7. Webpack笔记(三)——一款破产版脚手架的开发
  8. 【通信协议学习】关于Xmodem、Ymodem、Zmodem、ASCII、Binary传输协议
  9. Mimics-基础操作教程-1
  10. 纬地服务器找不带计算机,纬地V6.9升级启动解决方法大全
  11. 微信后台服务器能查撤回的消息吗,微信撤回的消息还能看到吗?查看方法介绍...
  12. DNK编程 JNI 之 javah
  13. Python制作微信小助手
  14. 设计模式回顾——模板模式(C++)
  15. 腾讯云国外服务器2核4G服务器新用户全攻略
  16. 富途最新股权曝光:腾讯持股21% 李华有67.4%投票权
  17. 塑胶模具设计中的几个小问题,学会不吃亏
  18. Code Snippets 使用
  19. 解决C#读取文本文件乱码
  20. 生产者消费者模型的作用是什么

热门文章

  1. Zynq和FPGA区别——快速认识Zynq开发
  2. 机房计算机没游戏,一款童年必玩的游戏,小学机房肯定安装,如今都不一定能过关!...
  3. Sketch中英文切换教程
  4. Github项目文档的管理
  5. 学计算机专业还是数学专业课,数学专业的数学和计算机专业的数学的比较.doc...
  6. python 结构体指针_C语言结构体指针(指向结构体的指针)详解
  7. 网络基础---广域网技术
  8. K_A02_005 基于单片机驱动数码管 LED 按键模块(TM1638) 流水灯 0-7 按键值显示
  9. prev_permutation函数
  10. Android音频子系统(十三)------audio音频测试工具