Python基础 - 多线程(下)
上篇对多线程有一个初步的认识, 常用的要点, 也是对照这 多进程 来试验的. 目的呢, 还是再不断地提醒自己能通俗理解进程和线程的"关系", OS -> 多进程 -> 多线程, (进程 : 线程 , 1 : n) 的关系. 当然从应用的角度, 会考虑到多线程该如何代码实现, 以及几个特性, 如 "主线程会默认等待子线程结束, 才结束", 这跟进程一样的. 于是有了 daemon 的概念...
下篇呢, 主要从多线程的 共享全局变量 从而引发 资源竞争 问题等, 来对比下多线程和多进程...
CPython 下, 并没有真正的多线程, 这个是历史性的问题哦, 暂且不谈.
多线程 - 共享全局变量
这点跟多进程不同, 多进程不共享全局变量, 通信方式呢, 可以用一个消息队列的方式不断去 "监听" .
# 多线程共享全局变量
import time
import threading
lst = []
def add_data():
for i in range(5):
print("add data:", i)
lst.append(i)
time.sleep(2)
def get_data():
while len(lst) <= 4: # 测试而已
print("get data: ", lst)
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=add_data)
t2 = threading.Thread(target=get_data)
t1.start()
t2.start()
print("main threading done")
add data: 0
get data: [0]
main threading done
get data: [0]
add data: 1
get data: [0, 1]
get data: [0, 1]
add data: 2
get data: [0, 1, 2]
get data: [0, 1, 2]
add data: 3
get data: [0, 1, 2, 3]
get data: [0, 1, 2, 3]
add data: 4
可将跟进程不同, 多线程是共享全局变量的哦.
# 多进程 不共享全局变量
import time
import multiprocessing
lst = []
def add_data():
for i in range(5):
print("add data:", i)
lst.append(i)
time.sleep(2)
def get_data():
while len(lst) <= 4:
print("get data: ", lst)
time.sleep(1)
if __name__ == '__main__':
t1 = multiprocessing.Process(target=add_data)
t2 = multiprocessing.Process(target=get_data)
t1.start()
t2.start()
print("main processing done")
main processing done
add data: 0
get data: []
get data: []
add data: 1
get data: []
get data: []
add data: 2
get data: []
get data: []
add data: 3
get data: []
当然, 关于多线程和多进程在全局变量共不共享的问题上, 其实各有各的特点和通途. 至于多进程不能共享, 因为其实不同的对象嘛或者程序我感觉. 同时能, 不共享能降低程序的耦合性, 也不会增加逻辑理解上的难度哦. 我觉得是设计蛮好的, 原理也暂时不深究. 只是从应用上, 如果非要共享, 建议还是用队列 的方式来作为通信渠道.
而多线程就不是这样子, 是可以资源共享的, 那必然会带来一个大的问题, 资源的竞争.
子线程资源竞争
import time
import threading
count = 0
def task_01():
for _ in range(1234567):
global count
count += 1
print("count task_02:", count)
def task_02():
for _ in range(1234567):
global count
count += 1
print("count task_01:", count)
if __name__ == '__main__':
t1 = threading.Thread(target=task_01)
t2 = threading.Thread(target=task_02)
t1.start()
t2.start()
count task_02: 1405859
count task_01: 1788620
一看这个数据就不对. 两个线程对 全局变量 count 进行操作时, 状态就乱了呀, 这就是资源竞争的问题. 线程是共享全局变量的, 因此会有这种情况的. 那最简单的解决办法, 就是 线程同步, 保证在同一时刻, 只能有一个线程去对全局加变量进行处理.
线程等待
写法上, 即对某个进程, 调用 join( ), 如果这样, 也就变成了单进程了.
import time
import threading
count = 0
def task_01():
for _ in range(1234567):
global count
count += 1
print("count task_02:", count)
def task_02():
for _ in range(1234567):
global count
count += 1
print("count task_01:", count)
if __name__ == '__main__':
t1 = threading.Thread(target=task_01)
t2 = threading.Thread(target=task_02)
t1.start()
t1.join() # 等 t1 执行完了再执行 t2, 没有多线程了.
t2.start()
count task_02: 1234567
count task_01: 2469134
这样又变回了单进程, 没有啥意义了.
互斥锁
锁的目的, 即为了在多线程中, 防止资源竞争的问题, 但同时也 降低了运行效率 和 容易 死锁.
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
import threading
# 创建锁, 默认开启
my_lock = threading.Lock()
count = 0
def task_01():
for _ in range(1234567):
global count
# acquire(), 进入锁定的状态
my_lock.acquire()
count += 1
# release(), 解锁
my_lock.release()
print("count task_02:", count)
def task_02():
for _ in range(1234567):
global count
my_lock.acquire()
count += 1
my_lock.release()
print("count task_01:", count)
if __name__ == '__main__':
t1 = threading.Thread(target=task_01)
t2 = threading.Thread(target=task_02)
t1.start()
t2.start()
就到这吧, 多线程的基本点也差不多了, 不想再弄所谓 "死锁" 的情况, 这些都很直观, 就你在某个时刻, 让其进入 锁定的状态, 然后忘了 release, 肯定就被锁死了呀. 就不演示 demo 了, 线程也几乎很少在用, 原因是我用的 CPython的解释器, 有一个历史的bug, 里面好像是说, 就已经有了一个 "锁" 的概念, 因此是没有真正的实现多线程的. 因而我基本不会用, 一般用多进程来整, 反正电脑配置好, 资源随便玩, 不差这点...
小结
对于线程和进程, 还可以稍微来比较一下. 一个进程只要有一个线程, 就是这样的关系. 从资源占用的角度来决策, 多进程是 cpu 调度的嘛, 因此多进程资源开销是比较大的, 线程在进程内部, 资源开销是有进程决定的呀. 现在咱的电脑都是多核的, 如果用多进程, 则可充分利用 多核资源. 必须要突出一点很重要的区别:
- 多进程不共享全局变量 (当然,可借助队列实现通信)
- 多线程能共享全局变量 (然而, 带来了资源竞争的问题, 可以一用 互斥锁 (Lock) 来解决
总之不论从写代码角度还是程序的稳定性上, 多进程肯定是占优势的, 我也是优先推荐. 至于资源占用大的问题, 我想, 公司的服务器, 似乎不差我这一点点占用吧.
Python基础 - 多线程(下)的更多相关文章
- 吾八哥学Python(四):了解Python基础语法(下)
咱们接着上篇的语法学习,继续了解学习Python基础语法. 数据类型大体上把Python中的数据类型分为如下几类:Number(数字),String(字符串).List(列表).Dictionary( ...
- python --- 基础多线程编程
在python中进行多线程编程之前必须了解的问题: 1. 什么是线程? 答:线程是程序中一个单一的顺序控制流程.进程内一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程 ...
- Python基础(下)
前言 print("\n".join([''.join(['*'*((x-y)%3) if((x*0.05)**2+(y*0.1)**2 -1)**3-(x*0.05)**2*(y ...
- python基础===多线程
https://www.cnblogs.com/wj-1314/p/8263328.html threading 模块 先上代码: import time, threading def loop(): ...
- Python基础-多线程与多进程
一,线程与进程之间的关系:(从知乎上看到的) 一个必须知道的事实:执行一段程序代码,实现一个功能的过程介绍 ,当得到CPU的时候,相关的资源必须也已经就位,就是显卡啊,GPS啊什么的必须就位,然后CP ...
- python基础15下_迭代器_生成器
print(dir([])) #告诉我列表拥有的所有方法 # 双下方法 # print([1].__add__([2])) print([1]+[2]) ret = set(dir([]))& ...
- Python基础Day1—下
六.Python运行 print() 打印命令,输出到屏幕上 操作: 命令提示符-->输入Python-->文件路径 若输入Python回车报错或者提示没有,则Python解释器没有安 ...
- python学习笔记(11)--测验3: Python基础语法(下) (第7周)
斐波那契数列计算 B 描述 斐波那契数列如下: F(0) = 0, F(1) = 1 F(n) = F(n-1) + F(n-2) 编写一个计算斐波那契数列的函数,采用递归方式,输出不超过n的所有斐波 ...
- Python基础知识(Basic knowledge)
Python基础知识(Basic knowledge) 1.认识Python&基础环境搭建 2.Python基础(上) 3.Python基础(中) 4.Python基础(下) 5.Python ...
- Python基础(1) - 初识Python
Python 特点: 1)面向对象 2)解释执行 3)跨平台.可移植 4)垃圾回收机制 5)动态数据类型.强类型 6)可扩展.可嵌入 Python可以方便调用C/C++等语言,同时也可以方便的被C/C ...
随机推荐
- [luogu1248] 加工生产调度 题解
考虑 \(i\) 排在 \(j\) 前的条件是 \(a_i+\max(a_j,b_i)+b_j\le a_j+\max(a_i,b_j)+b_i\),然后发现这一坨东西是皇后游戏中的倒数第三个式子,直 ...
- STC15F104E的外部中断工作异常
STC15F104E使用了外部中断,发现中断工作有时会失效,必需重新上电才能恢复,使用中不时会失效. 1 /********************************************** ...
- 数据挖掘 | 数据隐私(3) | 差分隐私 | 差分隐私概论(上)(Intro to Differential Privacy 1)
L3-Intro to Differential Privacy 从这节课开始就要介绍差分隐私算法了. 随机响应(Randomized Response) 场景提出 假若你是某一门课的教授,你希望统计 ...
- 中国联通校园招聘:软件研究院Offer面经
本文介绍2024届春招中,中国联通软件研究院广州分院的软件研发岗位的3场面试基本情况.提问问题等. 2024年03月投递了中国联合网络通信有限公司下属软件研究院的软件研发岗位,所在部门为广州分 ...
- ocr识别过程中报错 tesseract is not installed
这个问题无论在初始编译时或者在后来坏境变更调试时都会遇到的问题. 解决:问题原因是源码中的默认路径位置与文件位置不同,需要更改一下
- 大模型基础补全计划(二)---词嵌入(word embedding)
PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明 无 前言 本文是这个系列第二篇,它们是: &l ...
- SSH远程神器—Termius
简介 Termius是一款非常好用而且漂亮的SSH客户端,能快速远程控制服务器,可以定制自己喜欢的主题.Termius不仅涵盖了PC端的Windows.Linux.Mac,还支持手机端的Android ...
- Netty源码—8.编解码原理
大纲 1.读数据入口 2.拆包原理 3.ByteToMessageDecoder解码步骤 4.解码器抽象的解码过程总结 5.Netty里常见的开箱即用的解码器 6.writeAndFlush()方法的 ...
- 用于线程同步的Interlocked系列函数主要有哪些
原子访问 通过Interlocked系列函数是 Windows API 提供的一组原子操作函数,用于在多线程环境中安全地操作共享变量.当我们执行这些Interlocked系列函数的时候 ,函数会对总线 ...
- Windows Server 2012 配置 FTP
环境 Windows Server 2012 安装步骤 打开服务器管理器 管理 - 添加角色和功能 开始之前 选择安装类型 服务器选择 选择对应的服务器... 服务器角色 功能 确认 安装进度 配置步 ...