多线程面试题汇总
多线程面试题汇总
一、多线程
1、线程的生命周期

1、线程的创建:t=threading.Thread()
2、就绪状态:已经获得了除CPU之外的其他资源,正在参与调度,等待被执行,当调度完成之后,立即运行
3、启动状态:获得了CPU时间片段,正在运行
4、等待\阻塞状态:遇到time.sleep()时,会阻塞,暂时不参与调度,等待事件发生
5、中止状态:线程运行结束,run函数运行结束,等待系统回收其线程资源。
2、线程的创建(函数创建)


3、线程的创建(使用类)
t=MyThread(name=s[i]) 创建线程,里面的参数代表线程的名字,如果不传,系统会默认有一个名字

4、守护线程
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程—起退出,这时就可以用setDaemon方法

输出结果:线程2还没有完整的执行完毕,遇到守护线程就终止执行了。

t.setDaemon(True) 当前的子线程设置为守护线程
守护线程:随着主线程的终止而终止,不管当前主线程下有多少子线程没有执行完毕,都会终。
二、全局解释器锁
1、GIL锁不是python的特点。而是cpython的特点。
每个线程在执行的时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU。在CPython中,每一个Python线程执行前都需要去获得GIL锁,获得该锁的线程才可以执行,没有获得的只能等待,当具有GIL锁的线程运行完成后,其他等待的线程就会去争夺GIL锁,这就造成了,在Python中使用多线程,但同一时刻下依旧只有一个线程在运行,所以Python多线程其实并不是「并行」的,而是「并发」。
1、使用单线程实现累加到500000000
import time,threadingdef task(n):sum=0while sum<n:sum+=1print(f'最后累加哦结果为:{sum}')if __name__ == '__main__':# 单线程start=time.time()task(500000000)end=time.time()print(f'单线程结束后,一共运行的时间为:{end-start}')

2、使用多线程实现累加到500000000
import time,threadingdef task(n):sum=0while sum<n:sum+=1print(f'最后累加哦结果为:{sum}')if __name__ == '__main__':# 多线程start = time.time()t1=threading.Thread(target=task,args=(250000000,))t2=threading.Thread(target=task,args=(250000000,))t1.start()t2.start()t1.join()t2.join()end=time.time()print(f'单线程结束后,一共运行的时间为:{end-start}') # todo 15.777813196182251

发现问题:两个线程同时执行并不能比单个线程的执行快
由此发现:CPU密集型(计算密集型)任务采用多线程执行并不能提高计算速度
3、总结
GIL解决办法:
- 使用其他语言写的python解析器(不推荐,还是用python官方的CPython好)
- JPython、pypy
- 不使用多线程,使用多进程-进程里面加协程实现多任务来充分利用多核CPU(推荐)
- 即使存在GIL,在有IO等待操作的程序中,还是多线程快;当然没有资源等待的还是使用单线程快(科学计算、累加等等)
但是需要注意的是线程有了GIL后并不意味着使用python多线程时不需要考虑线程安全,GIL的存在是为了方便使用C语言CPython解释器的编写者,而顶层使用python时依旧要考虑线程安全。
三、线程安全
当多个线程同时访问一个对象时,不管如何计算,如果调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。如果出现了“脏数据”。则线程不安全。
脏数据:产生脏数据的原因是,当一个线程在对数据进行修改时,修改到一半时另一个线程读取了未经修改的数据并进行修改。
如何避免脏数据的产生呢?一个办法就是用join方法,即先让一个线程执行完毕再执行另一个线
程。但这样的本质是把多线程变成了单线程,失去了多线程的意义。另一个办法就是用线程锁。
1、多线程之数据混乱问题


但是当我把累加次数设置小,就不会出现数据混乱问题

数据混乱的原因:
cpu分成多个时间片段,启动10线程,分配10个cpu时间片段,当我累加数字设置比较小的时候,在单个cpu时间片段内,for循环代码就执行完,就不会产生数据混乱的。当我数据设置的比较大时,在单个cpu时间片段内,for循环代码就执行不完,并且没有分配2个或2个以上的连续的cpu时间片段,导致一个cpu时间片段没有执行完该线程,下一个线程开始执行了
2、有了全局解释器锁(GIL)为什么还需要同步锁?
全局解析器锁(GIL)加在了全局了,没有加到我所想要的位置,加到什么位置不是我们决定的;
包括修改资源的程序和非修改资源的程序,如果出现在修改资源的相关代码上,肯定会出现脏数据。
同步锁:来获取一把互斥锁。互斥锁就是对共享数据进行锁定,保证同一时刻只有一个线程操作数据,是数据级别的锁。
GIL锁是解释器级别的锁,保证同一时刻进程中只有一个线程拿到GIL锁,拥有执行权限。
2个线程执行任务造成数据混乱
import threading
num=0
def work():global numfor i in range(1000000):num+=1print('work',num)def work1():global numfor i in range(1000000):num+=1print('work1',num)if __name__ == '__main__':t1=threading.Thread(target=work)t2=threading.Thread(target=work1)t1.start()t2.start()t1.join()t2.join()print('主线程执行结果',num)
执行结果:明显数据混乱了
work work1 16347351376208
主线程执行结果 1634735
说明:
代码中:num+=1,可以拆解为
num=100
100+1
num=101
同步锁这3行代码执行完毕了才会释放锁
3、同步锁
同一时刻的一个进程下的一个线程只能占用CPU,要确保这个线程下的程序在一段时间内被CPU执行,那么就要用到同步锁,只需要在对公共数据的操作前后加上上锁和释放锁的操作即可。
3.1、加同步锁(with方法)
from threading import Lockimport threading
num=0
def work():global numfor i in range(1000000):with lock:num+=1print('work',num)def work1():global numfor i in range(1000000):with lock:num+=1print('work1',num)if __name__ == '__main__':lock=Lock()t1=threading.Thread(target=work)t2=threading.Thread(target=work1)t1.start()t2.start()t1.join()t2.join()print('主线程执行结果',num)
work 1845334work1 2000000
主线程执行结果 2000000
3.2、加同步锁(acquire方法和release方法)
from threading import Lockimport threading
num=0
def work():global numfor i in range(1000000):lock.acquire()num+=1lock.release()print('work',num)def work1():global numfor i in range(1000000):lock.acquire()num += 1lock.release()print('work1',num)if __name__ == '__main__':lock=Lock()t1=threading.Thread(target=work)t2=threading.Thread(target=work1)t1.start()t2.start()t1.join()t2.join()print('主线程执行结果',num)
workwork1 1921583
2000000
主线程执行结果 2000000
四、锁
锁是python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变得有序。
一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变得可控,所以若要保证线程安全,就必须使用锁。
threading模块中提供了5种最常见的锁,下面是按照功能进行划分:
- 同步锁:Lock(一次只能放行一个)
- 递归锁:RLock(一次只能放行一个)
- 条件锁:condition(一次可以放行任意个)
- 事件锁:event(一次全部放行)
- 信号量锁:semaphore(一次可以放行特定个)
1、同步锁
上面已讲解
2、递归同步锁
在同步锁的基础上可以做到连续重复使用多次acquire()后再重复使用多次release()的操作,但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。
递归锁RLock:它内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
3、条件锁
条件锁是在递归锁的基础上增加了能够暂停线程运行的功能。并且我们可以使用wait()和notify()来控制线程执行的个数。
注意:条件锁可以自由设定一次放行几个线程。
lock.notify(number):有条件的唤醒线程
lock.wait() :让当前线程暂停。并等待
但是不能唤醒特定某个线程
from threading import Thread,Condition,current_thread#全局变量
g_number = 0#子线程的数量
sub_thread_count=10#正在运行的线程数量
current_run_thread_count=0#创建一个条件锁
lock=Condition()def task():global g_number,current_run_thread_countthread_name=current_thread().name #当前线程的名字with lock:print(f'{thread_name}线程得到锁,并直接进入等待状态')lock.wait() #让当前线程暂停。并等待print(f'{thread_name}线程已经苏醒了,并执行后面的代码')g_number+=1current_run_thread_count+=1if __name__ == '__main__':thread_list=[]for i in range(sub_thread_count):t=Thread(target=task,name=f't{i+1}')thread_list.append(t)t.start()while current_run_thread_count<sub_thread_count:# 由用户输入number=int(input("请输入你要唤醒几个线程,数字:"))with lock:lock.notify(number)print('主线程执行结束')
运行代码
每个线程都能得到这把锁,并进入等待状态,此时线程到13行代码已经阻塞了。10个线程都已经调用了wait()函数

现在需要唤醒4个线程,输入数字4,t1,t2,t3,t4线程被唤醒

再唤醒4个线程,t5~t8被唤醒

4、事件锁
事件锁是基于条件锁来做的,它与条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。
我们可以将事件锁看为红绿灯,当红灯时所有子线程都暂停运行,并进入“等待”状态,当绿灯时所有子线程都恢复“运行”
eventLock.set() 设置绿灯
eventLock.clear() 设置红灯
eventLock.wait() 暂停运行,等待绿灯
import threadingmaxSubThreadNumber = 3 #最多子线程为3def task():thread_name = threading.current_thread().name #获取当前线程的名字print("线程开始启动,并马上进入等待状态:%s" % thread_name)eventLock.wait() # 暂停运行,等待绿灯print("第一次绿灯打开,线程往下走:%s " % thread_name)eventLock.wait() # 暂停运行,等待绿灯print("第二次绿灯打开,线程往下走:%s" % thread_name)if __name__ == '__main__':eventLock = threading.Event()for i in range(maxSubThreadNumber):subThreadIns = threading.Thread(target=task)subThreadIns.start()eventLock.set() #设置绿灯eventLock.clear() #设置红灯eventLock.set()

5、信号量锁
Semaphore()
信号量锁也是根据条件锁来做的,它与条件锁的区别如下:
条件锁:一次可以放行任意个处于 “等待” 状态的线程
事件锁:一次可以放行全部的处于 “等待” 状态的线程
信号量锁:通过规定,成批的放行特定个处于 “上锁” 状态的线程
import threading
import timemaxSubThreadNumber = 6def task():thread_name = threading.currentThread().namewith semaLock:print("线程获得锁,开始运行:%s" % thread_name)time.sleep(3)if __name__ == '__main__':semaLock = threading.Semaphore(2)for i in range(maxSubThreadNumber):subThreadIns=threading.Thread(target=task)subThreadIns.start()
五、死锁
在多线程程序中,死锁问题很大一部分原因是由于线程同时获取多个锁造成的。
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
3、尽管死锁很难发生,但一旦发生就会造成应用的停止响应。

1、案例:鱼和熊掌不可兼得问题
import threading
import time# 代表鱼的锁
mutex_Yu = threading.Lock()
# 代表熊掌的锁
mutex_Xiongzhang = threading.Lock()class MyThread1(threading.Thread):def run(self):mutex_Yu.acquire() # 得到🐟print('线程1已经得到鱼了')time.sleep(1)mutex_Xiongzhang.acquire()print('线程1得到熊掌')mutex_Xiongzhang.release()mutex_Yu.release()class MyThread2(threading.Thread):def run(self):mutex_Xiongzhang.acquire()print('线程2已经得到熊掌了')time.sleep(1)mutex_Yu.acquire()print('线程2已经得到🐟了')mutex_Yu.release()mutex_Xiongzhang.release()if __name__ == '__main__':t1 = MyThread1()t2 = MyThread2()t1.start()t2.start()

解决方法:让多个线程交叉有序的竞争多个资源
#让多个线程交叉有序的竞争多个资源
import threading
import time
# 代表🐟的锁
mutex_Yu = threading.Lock()
# 代表熊掌的锁
mutex_Xiongzhang = threading.Lock()class MyThread1(threading.Thread):def run(self):while True:mutex_Yu.acquire() # 得到🐟print('线程1已经得到鱼了')time.sleep(1)mutex_Yu.release() #释放鱼对应锁mutex_Xiongzhang.acquire() # 得到熊掌print('线程1得到熊掌')time.sleep(1)mutex_Xiongzhang.release() #释放熊掌对应锁class MyThread2(threading.Thread):def run(self):while True:mutex_Xiongzhang.acquire() # 得到熊掌print('线程2已经得到熊掌了')time.sleep(1)mutex_Xiongzhang.release() #释放熊掌对应锁mutex_Yu.acquire() # 得到🐟print('线程2已经得到鱼了')time.sleep(1)mutex_Yu.release() #释放鱼对应锁if __name__ == '__main__':t1 = MyThread1()t2 = MyThread2()t1.start()t2.start()
六、进程与线程的区别
1、进程是操作系统分配任务的基本单位,进程是python中正在运行的程序;当我们打开1个浏览器时就是开启了一个浏览器进程;
线程是进程中执行任务的基本单位(执行指令集),一个进程中至少有一个线程,当只有一个线程时,称为主线程。
2、进程的创建和销毁消耗资源多;
线程的创建和销毁消耗资源少。
3、线程的切换速度比较快。
4、多进程中,进程与进程之间不能进行通信,如果需要通信需要借助Queue、Pipe;
一个进程中有多个线程时:线程之间可以进行直接通信。
5、多进程可以利用多核CPU:多进程的主要目的是充分利用多核CPU资源。
多线程不可以利用多核CPU:多线程的主要目的是充分利用好某一个单核。
6、进程的启动速度要比线程的启动速度慢。
7、进程与进程是不可以共享同一个数据的(同一个全局变量);
两个线程可以共享同一个进程中的一个数据(同一个全局变量)。
8、进程用到了Process类;
线程用到了Thread类。
9、进程可以独立存在;
线程不能独立存在,依赖进程存在。
10、多进程中不会出现数据混乱、死锁的问题;
多线程中会出现数据混乱、死锁的问题。

相关文章:
多线程面试题汇总
多线程面试题汇总 一、多线程1、线程的生命周期2、线程的创建(函数创建)3、线程的创建(使用类)4、守护线程 二、全局解释器锁1、使用单线程实现累加到5000000002、使用多线程实现累加到5000000003、总结 三、线程安全1、多线程之数…...
CentOS7.9+Kubernetes1.29.2+Docker25.0.3高可用集群二进制部署
CentOS7.9Kubernetes1.29.2Docker25.0.3高可用集群二进制部署 Kubernetes高可用集群(Kubernetes1.29.2Docker25.0.3)二进制部署二进制软件部署flannel v0.22.3网络,使用的etcd是版本3,与之前使用版本2不同。查看官方文档进行了解…...
STM32——OLED菜单(二级菜单)
文章目录 一.补充二. 二级菜单代码 简介:首先在我的51 I2C里面有OLED详细讲解,本期代码从51OLED基础上移植过来的,可以先看完那篇文章,在看这个,然后按键我是用的定时器扫描不会堵塞程序,可以翻开我的文章有单独的定时…...
配置Vite+React+TS项目
初始化 执行npm create vite并填写项目名、用那个框架。。 配置 路径别名 在vite.config.ts里面配置: import { defineConfig } from vite import react from vitejs/plugin-react import path from pathexport default defineConfig({plugins: [react()],reso…...
2.13:C语言测试题
21.(b) 6 22.(b) cd 23.b) 5 4 1 3 2 栈:先进后出 24. b,c,d:10,12,120 25.2,5 26.越界访问,可能正常输出,可能段错误 27. 0,41 28. a)11 b) 320 29. aab; ba-b; aa-b; 30. p150x801005; p250x810…...
ubuntu22.04 有一台机器说有4T硬盘,但是df的时候看不到,怎么查找
在 Ubuntu 22.04 上,如果你有一块硬盘在使用df命令时未显示,这通常意味着硬盘尚未被挂载或者根本未被分区和格式化。以下是一些步骤来帮助你识别和准备新硬盘: 1. 检查硬盘是否被系统识别 首先,使用lsblk命令来检查系统是否识别…...
【机器学习】数据清洗之识别重复点
🎈个人主页:甜美的江 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步…...
鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之NavDestination组件
鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之NavDestination组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、NavDestination组件 作为NavRouter组件的子组件,用于显示导…...
tokio tcp通信
引入crate tokio { version "1.35.1", features ["full"] } 服务端 use std::time::Duration; use tokio::{io::{AsyncBufReadExt, AsyncWriteExt},net::{tcp::{OwnedReadHalf, OwnedWriteHalf},TcpListener, TcpStream,},sync::mpsc, };#[tokio::ma…...
LCR 122. 路径加密【简单】
LCR 122. 路径加密 假定一段路径记作字符串 path,其中以 "." 作为分隔符。现需将路径加密,加密方法为将 path 中的分隔符替换为空格 " ",请返回加密后的字符串。 示例 1: 输入:path "a.ae…...
SpringUtils 工具类,方便在非spring管理环境中获取bean
应用场景: 1 可用在工具类中, 2 spring【Controller,service】环境中, 3 其中的一个方法getAopProxy可获得代理对象,需要将 EnableAspectJAutoProxy(exposeProxy true) 允许获取代理对象 import org.springframework.aop.framew…...
JavaWeb之请求
请求 客户端请求由ServletRequest类型的request对象表示,在HTTP请求场景下,容器提供的请求对象的具体类型为HttpServletRequest HTTP的请求消息分为三部分:请求行、请求头、请求正文。 请求行对应方法 // 获取请求行中的协议名和版本public S…...
VsCode中常用的正则表达式操作
在vscode中可以使用正则表达式来进行搜索内容,极大的方便了我们对大量数据中需要查看的信息进行筛选,使用正则搜索时点击 .* 此文章会持续补充常用的正则操作 1.光标选中搜索到的内容 将搜索的内容进行全选,举例:在如下文件中我需…...
ubuntu22.04@laptop OpenCV Get Started: 007_color_spaces
ubuntu22.04laptop OpenCV Get Started: 007_color_spaces 1. 源由2. 颜色空间2.1 RGB颜色空间2.2 LAB颜色空间2.3 YCrCb颜色空间2.4 HSV颜色空间 3 代码工程结构3.1 C应用Demo3.2 Python应用Demo 4. 重点分析4.1 interactive_color_detect4.2 interactive_color_segment4.3 da…...
mysql 查询性能优化关键点总结
MySQL查询性能优化是数据库管理的重要环节,良好的性能优化可以提高查询效率,降低系统负载。以下是一些关键点,用于优化MySQL查询性能: 1. 索引优化 索引是MySQL查询优化的重要手段,合理的索引可以大大…...
React - 分页插件默认是英文怎么办
英文组件的通用解决方案 这里以分页插件为例: 大家可以看到,最后的这个页面跳转提示文字为Go to,不是中文,而官网里面的案例则是: 解决方案: import { ConfigProvider } from antd; import zhCN from an…...
揭开Markdown的秘籍:引用|代码块|超链接
🌈个人主页:聆风吟 🔥系列专栏:Markdown指南、网络奇遇记 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 📋前言一. ⛳️Markdown 引用1.1 🔔引用1.2 🔔嵌套引用1.3 &…...
【C语言】Debian安装并编译内核源码
在Debian 10中安装并编译内核源码的过程如下: 1. 安装依赖包 首先需要确保有足够的权限来安装包。为了编译内核,需要有一些基础的工具和库。 sudo apt update sudo apt upgrade sudo apt install build-essential libncurses-dev bison flex libssl-d…...
使用 C++23 从零实现 RISC-V 模拟器(6):权限支持
本节内容增加了权限表示,设置了三种权限。当 cpu 初始化时默认的权限为 Machine 模式。接下来实现这三种特权模式,随后实现 sret 和 mret 指令。 RISC-V定义了三种特权等级,分别是用户态(User Mode)、监管态ÿ…...
针对某终端安全自检钓鱼工具的分析
前言 朋友微信找到我,说某微信群利用0day通告进行钓鱼,传播名为“终端安全自检工具”的恶意文件,然后还给了两个IP地址,如下: 咱们就来详细看看这个工具吧。 样本信息 拿到样本,样本的图标,如…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...
Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...

