上篇对多线程有一个初步的认识, 常用的要点, 也是对照这 多进程 来试验的. 目的呢, 还是再不断地提醒自己能通俗理解进程和线程的"关系", 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基础 - 多线程(下)的更多相关文章

  1. 吾八哥学Python(四):了解Python基础语法(下)

    咱们接着上篇的语法学习,继续了解学习Python基础语法. 数据类型大体上把Python中的数据类型分为如下几类:Number(数字),String(字符串).List(列表).Dictionary( ...

  2. python --- 基础多线程编程

    在python中进行多线程编程之前必须了解的问题: 1. 什么是线程? 答:线程是程序中一个单一的顺序控制流程.进程内一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程 ...

  3. Python基础(下)

    前言 print("\n".join([''.join(['*'*((x-y)%3) if((x*0.05)**2+(y*0.1)**2 -1)**3-(x*0.05)**2*(y ...

  4. python基础===多线程

    https://www.cnblogs.com/wj-1314/p/8263328.html threading 模块 先上代码: import time, threading def loop(): ...

  5. Python基础-多线程与多进程

    一,线程与进程之间的关系:(从知乎上看到的) 一个必须知道的事实:执行一段程序代码,实现一个功能的过程介绍 ,当得到CPU的时候,相关的资源必须也已经就位,就是显卡啊,GPS啊什么的必须就位,然后CP ...

  6. python基础15下_迭代器_生成器

    print(dir([])) #告诉我列表拥有的所有方法 # 双下方法 # print([1].__add__([2])) print([1]+[2]) ret = set(dir([]))& ...

  7. Python基础Day1—下

    六.Python运行 print()   打印命令,输出到屏幕上 操作: 命令提示符-->输入Python-->文件路径 若输入Python回车报错或者提示没有,则Python解释器没有安 ...

  8. python学习笔记(11)--测验3: Python基础语法(下) (第7周)

    斐波那契数列计算 B 描述 斐波那契数列如下: F(0) = 0, F(1) = 1 F(n) = F(n-1) + F(n-2) 编写一个计算斐波那契数列的函数,采用递归方式,输出不超过n的所有斐波 ...

  9. Python基础知识(Basic knowledge)

    Python基础知识(Basic knowledge) 1.认识Python&基础环境搭建 2.Python基础(上) 3.Python基础(中) 4.Python基础(下) 5.Python ...

  10. Python基础(1) - 初识Python

    Python 特点: 1)面向对象 2)解释执行 3)跨平台.可移植 4)垃圾回收机制 5)动态数据类型.强类型 6)可扩展.可嵌入 Python可以方便调用C/C++等语言,同时也可以方便的被C/C ...

随机推荐

  1. Springboot集成easypoi实现excel多sheet导出

    1.环境配置 <!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId&g ...

  2. 使用idea合并 dev分支合并到test分支

    这里展示将dev分支合并到test分支首先切换到test分支 按下图所示操作

  3. MybatisPlus - [04] 分页

    limit m,n.PageHelper.MyBatisPlus分页插件 001 || MybatisPlus分页插件 (1)引入maven依赖 <dependency> <grou ...

  4. 浅谈Processing中的 println() 打印输出函数[String]

    简单看一下Processing中的打印输出函数println()相关用法. 部分源码学习 /** * ( begin auto-generated from println.xml ) * * Wri ...

  5. k8s Error: failed to prepare subPath for volumeMount "custom-logo" of container "grafana"

    前言 使用 k8s 挂载卷文件时,使用了 hostPath,type: File volumeMounts: - mountPath: /usr/share/grafana/public/img/gr ...

  6. npm 如何更新项目最新依赖包

    NPM 是什么? Node 软件包管理器(NPM)提供了各种功能来帮助你安装和维护项目的依赖关系. 由于错误修复.新功能和其他更新,依赖关系可能会随着时间的推移而变得过时.你的项目依赖越多,就越难跟上 ...

  7. Xshell连接有跳板机(堡垒机)的服务器

    一.Xshell直连有跳板机的服务器 跳板机IP:112.74.163.161 端口号: 22     服务器IP:47.244.217.66 端口号:22 1. 新建跳板机会话 点击连接,主机和端口 ...

  8. js调用本地程序资源-兼容所有浏览器

    在网页上通过JavaScript调用本地程序,兼容IE8/9/10/11.Opera.Chrome.Safari.Firefox等所有浏览器,在做Web开发时经常会遇到需要调用本地的一些exe或者dl ...

  9. 环境配置-Git和GitLab

    Git安装 到官网下载安装包,直接一路next即可. https://git-scm.com/download/win 配置用户名和用户邮箱 $ git config --global user.na ...

  10. HTML5 转

    贴个图: