上篇对多线程有一个初步的认识, 常用的要点, 也是对照这 多进程 来试验的. 目的呢, 还是再不断地提醒自己能通俗理解进程和线程的"关系", 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. SHA1字符串加密

    使用SHA1算法,生成某个字符串的hash值作为该字符串所代表对象的唯一标识: Demo: using System; using System.Collections.Generic; using ...

  2. kubsphere应用系列(三)-创建手动流水线

    准备工作 1.1 创建凭证 1.2 添加代码仓库 第一步创建流水线   第二步配置流水线 1.1选择CI/CD模板 1.2删除多余阶段 1.3 配置git仓库信息     1.4配置docker仓库信 ...

  3. vue路由$router.push()的三种传参方式

  4. SpringBoot - [09] Restful风格接口方法&参数

    GetMapping.PostMapping.DeleteMapping.PutMapping是SpringBoot中常用的HTTP请求映射注解,它们分别对应HTTP协议中的GET.POST.DELE ...

  5. Redis集群(cluster模式)搭建(三主三从)

    上一篇搭建了一主二从,并加入了哨兵,任何一个节点挂掉都不影响正常使用,实现了高可用.仍然存在一个问题,一主二从每个节点都存储着全部数据,随着业务庞大,数据量会超过节点容量,即便是redis可以配置清理 ...

  6. 详解nginx配置url重定向-反向代理

    https://www.jb51.net/article/99996.htm 本文系统:Centos6.5_x64 三台主机:nginx主机,hostname: master.lansgg.com  ...

  7. (附体验地址)大模型知识引擎:AI 助手能否助力销售技能提升?

    体验地址:https://lke.cloud.tencent.com/webim_exp/#/chat/FAIMcM 腾讯云的大模型知识引擎本身定位于为企业客户及合作伙伴提供服务,因此我在探索如何最佳 ...

  8. Netty基础—4.NIO的使用简介

    大纲 1.Buffer缓冲区 2.Channel通道 3.BIO编程 4.伪异步IO编程 5.改造程序以支持长连接 6.NIO三大核心组件 7.NIO服务端的创建流程 8.NIO客户端的创建流程 9. ...

  9. Golang入门:协程(goroutine)

    goroutine goroutine 是 Go 的并发模型的核心概念.为了理解 goroutine,我们来定义几个术语.第一个是进程.进程是程序的实例,由计算机的操作系统运行.操作系统将一些资源(如 ...

  10. Go Module使用 六大场景讲解示例

    前言 通过学习Go是怎么解决包依赖管理问题的?.go module基本使用,我们掌握了 Go Module 构建模式的基本概念和工作原理,也初步学会了如何通过 go mod 命令,将一个 Go 项目转 ...