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 ...
随机推荐
- c++用正则表达式判断匹配字符串中的数字数值(包括负数,小数,整数)MFC编辑框判断数值
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/12097381.html 因为今天做那个MFC的编辑框有一些框就是要判断输入的是否是数值,一开始 ...
- MybatisPlus - [04] 分页
limit m,n.PageHelper.MyBatisPlus分页插件 001 || MybatisPlus分页插件 (1)引入maven依赖 <dependency> <grou ...
- MySQL - [08] 存储过程
题记部分 一.什么是存储过程 存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效 ...
- docker - [12] 镜像发布到DockerHub、阿里云
题记部分 一.镜像发布到 DockerHub 1.地址:https://hub.docker.com/ 注册自己的账号 2.确定这个账号可以登录 3.在服务器上提交镜像 4.登录之后提交镜像即可. [ ...
- SQLSugar 支持 TDengine 超级表的使用指南
TDengine 是一款高性能.分布式的时序数据库,广泛应用于物联网.工业互联网等领域.其核心概念之一是超级表(Super Table),它类似于传统数据库中的表结构模板,允许用户通过标签(Tag)动 ...
- PHP测试代码执行时间
https://blog.csdn.net/wyqwclsn/article/details/39930125 非常简单代码开始前加一个$start = microtime(true);代码结束后加一 ...
- linux下配置ip为动态获取
点击查看代码 在Linux系统中配置网络接口以动态获取IP地址,通常需要使用DHCP(Dynamic Host Configuration Protocol).大多数现代Linux发行版都默认支持这个 ...
- U盘制作、安装Ubuntu系统
制作 ubuntu U盘启动盘 下载Ubuntu镜像 打开 Ubuntu 官网:https://ubuntu.com/download/desktop ,进入页面后,点击右边的[Download]按钮 ...
- StarRocks 升级注意事项
前段时间升级了生产环境的 StarRocks,从 3.3.3 升级到了 3.3.9,期间还是踩了不少坑所以在这里记录下. 因为我们的集群使用的是存算分离的版本,也是使用官方提供的 operator 部 ...
- 安装Realtek RTL8111/RTL8168网卡驱动详解(error~eth0:no such device)
昨天给linux系统重新编了个内核linux2.6.31.9,进入新版本的内核之后,发现机子上不了网了.好像每次新编译一个内核版本,网卡都会出问题,之前也写过解决网卡问题的blog,不过比较简单,这里 ...