Python线程详解

Python线程详解Python 线程

线程简介

  • 线程,有时被称为轻量进程(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())有相同的结果。

在这里插入图片描述

开启多线程

import threading import time def 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() 

Python线程详解

线程之间共享

import threading money = 1000 def run1(): global money for i in range(100): money -= 1 def run2(): global money for 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 sleep n = 0 def task1(): global n for i in range(): n += 1 print('---->task1中的值是:', n) def task2(): global n for i in range(): n += 1 print('---->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 sleep lock = threading.Lock() list1 = [0] * 10 def task1(): lock.acquire() for i in range(len(list1)): list1[i] = 1 sleep(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 sleep lockA = 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 sleep def produce(q): i = 0 while i < 10: num = random.randint(1, 100) q.put("生产者已产生的数据是%d" % num) print("生产者产生的数据为:%d" % num) sleep(1) i += 1 q.put(None) q.task_done() def consume(q): i = 0 while True: item = q.get() if item is None: break print('消费者获取的数据:', 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中引用了这个类。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/215584.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午1:55
下一篇 2026年3月18日 下午1:55


相关推荐

  • 腾讯混元T1-Vision上线元宝:一张图片就能分析出产品研发成本

    腾讯混元T1-Vision上线元宝:一张图片就能分析出产品研发成本

    2026年3月12日
    2
  • Ubuntu系统下安装SQLite Browser教程[通俗易懂]

    Ubuntu系统下安装SQLite Browser教程[通俗易懂]一、参考资料InstallSQLiteandSQLiteBrowseronUbuntu18.04LTSLinux下安装可视化数据库浏览器DBBrowserforSQLite3.37ubuntu安装sqlite3二、相关介绍SQLiteDBBrowser是一个强大的与SQLite数据库交互的工具。它被开发人员和最终用户使用。SQLiteDB浏览器不是为SQLite设计的,也不需要了解SQL。它只是一个帮助用户使用SQLite数据库的可视化工具。

    2025年10月12日
    7
  • 爱心代码(c语言实现)

    爱心代码(c语言实现)爱心代码 c 语言实现 哈哈 属于我们程序猿的浪漫 快去分享给你心中最可爱的 TA 吧

    2026年3月19日
    2
  • 关于pycharm安装库失败的解决方案

    关于pycharm安装库失败的解决方案使用 pycharm 安装第三方库 打开 pycharm 工具 gt 点击 File gt 点击 Settings gt 点击 Project xxx gt ProjectInter gt 点击右侧边框右上角的 gt 在弹出的 AvailablePac 的输入框中输入需要安装的库名在安装 numpy 这个库时 没能安装成功 查看了下错误信息 error MicrosoftVis 14 0isrequired Getitwi

    2026年3月27日
    0
  • scsa笔记1

    scsa笔记1常见的网络安全术语0day通常是指还没有补丁的漏洞。也就是说官方还没有发现或者是发现了还没有开发出安全补丁的漏洞exploit简称exp,漏洞利用APT攻击高级持续性威胁。利用先进的攻击手段对特定目标进行长期持续性网络攻击的攻击形式1.1信息安全脆弱性及常见安全攻击网络环境的开放性在这里插入图片描述协议栈的脆弱性及常见攻击截获嗅探(sniffing)监听(eavesdropping)篡改数据包篡改(tampering)中断拒绝服务(dosing)伪造欺骗(spoof

    2022年6月20日
    42
  • fillna函数用法_fill…with

    fillna函数用法_fill…withinplace参数的取值:True、FalseTrue:直接修改原对象False:创建一个副本,修改副本,原对象不变(缺省默认)method参数的取值:{‘pad’,‘ffill’,‘backfill’,‘bfill’,None},defaultNonepad/ffill:用前一个非缺失值去填充该缺失值backfill/bfill:用下一个非缺失值填充该缺失…

    2022年8月12日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号