多线程
多线程
1.今日概要
- 爬虫
- 线程的概念及与进程的区别?
- 多线程的应用
- 线程安全(单例模式)
- GIL(全局解释器锁)
2. 内容回顾&补充
-
面向对象继承
class Thread(object): def __init__(self): pass def start(self): self.run() def run(self): print('thread.run') obj = Thread() obj.start() ----------------------------------- class MyThread(Thread): def run(): print('mythread.run') obj = MyThread() obj.start()
-
面向对象的上下文管理
class Foo: def __enter__(...): pass def __exit__(...): pass lock = Foo() with lock: pass
-
单例模式
class Foo: instance = None def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): # 返回空对象 if cls.instance: return cls.instance cls.instance = object.__new__(cls) return cls.instance obj1 = Foo('日魔') obj2 = Foo('SB') print(obj1,obj2)
-
socketserver的实现原理
内部创建进程或线程实现并发.
3. 今日详细
3.1 爬虫下载图片的案例
-
安装和使用
1. 安装第三方模块 pip3 install requests 2. 使用 import requests url = 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg' # python伪造浏览器向地址发送请求 rep = requests.get(url) # 请求返回回来的字节 # print(rep.content) with open('xxxxxx.jpg',mode='wb') as f: f.write(rep.content)
-
下载图片示例
import requests url_list = [ 'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg', 'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg', 'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg' ] for url in url_list: ret = requests.get(url) file_name = url.rsplit('/',maxsplit=1)[-1] with open(file_name,mode='wb') as f: # 下载小文件 # f.write(ret.content) # 下载大文件 for chunk in ret.iter_content(): f.write(chunk)
注意:以上代码是基于串行来实现.
3.2 线程的概念&与进程的区别?
形象的关系
- 工厂 -> 应用程序
- 车间 -> 进程
- 工人 -> 线程
进程和线程的区别?
进程是计算机资源分配的最小单位.
线程是计算机中可以被cpu调度的最小单位.
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源,一个进程中至少有一个线程(一个应用程序中至少有一个进程)
在Python中因为有GIL锁,他同.....
默认进程之间无法进行资源共享,如果主要想要通讯可以基于:文件/网络/Queue.
3.3 多线程的应用
-
快速应用
import threading def task(arg): pass # 实例化一个线程对象 t = threading.Thread(target=task,args=('xxx',)) # 将线程提交给cpu t.start()
import threading def task(arg): ret = requests.get(arg) file_name = arg.rsplit('/', maxsplit=1)[-1] with open(file_name, mode='wb') as f: f.write(ret.content) for url in url_list: # 实例化一个线程对象 t = threading.Thread(target=task,args=(url,)) # 将线程提交给cpu t.start()
-
常见方法
-
t.start() ,将线程提交给cpu,由cpu来进行调度.
-
t.join() , 等待
import threading # 示例1 """ loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 t = threading.Thread(target=_add,args=(loop,)) t.start() t.join() print(number) """ # 示例2 """ loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 def _sub(count): global number for i in range(count): number -= 1 t1 = threading.Thread(target=_add,args=(loop,)) t2 = threading.Thread(target=_sub,args=(loop,)) t1.start() t2.start() print(number) """ # 示例3 """ loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 def _sub(count): global number for i in range(count): number -= 1 t1 = threading.Thread(target=_add,args=(loop,)) t2 = threading.Thread(target=_sub,args=(loop,)) t1.start() t2.start() t1.join() # t1线程执行完毕,才继续往后走 t2.join() # t2线程执行完毕,才继续往后走 print(number) """ # 示例4 """ loop = 10000000 number = 0 def _add(count): global number for i in range(count): number += 1 def _sub(count): global number for i in range(count): number -= 1 t1 = threading.Thread(target=_add,args=(loop,)) t2 = threading.Thread(target=_sub,args=(loop,)) t1.start() t1.join() # t1线程执行完毕,才继续往后走 t2.start() t2.join() # t2线程执行完毕,才继续往后走 print(number) """
-
t.setDaemon() ,设置成为守护线程
import threading import time def task(arg): time.sleep(5) print('任务') t = threading.Thread(target=task,args=(11,)) t.setDaemon(True) t.start() print('END')
-
线程名称的设置和获取
import threading def task(arg): # 获取当前执行此代码的线程 name = threading.current_thread().getName() print(name) for i in range(10): t = threading.Thread(target=task,args=(11,)) t.setName('日魔-%s' %i ) t.start()
-
run() , 自定义线程时,cpu调度执行的方法
class RiMo(threading.Thread): def run(self): print('执行此线程',self._args) obj = RiMo(args=(100,)) obj.start()
-
练习题: 基于socket 和 多线程实现类似于socketserver模块的功能.
import socket
import threading
def task(connect,address):
pass
server = socket.socket()
server.bind(('127.0.0.1',9000))
server.listen(5)
while True:
conn,addr = server.accept()
# 处理用户请求
t = threading.Thread(target=task,args=(conn,addr,))
t.start()
3.4 线程安全
多个线程同时去操作一个"东西",不要存在数据混乱.
线程安全: logging模块 / 列表
线程不安全: 自己做文件操作 / 同时修改一个数字
使用锁来保证数据安全,来了多个线程,使用锁让他们排队,逐一执行.
-
Lock
import threading import time num = 0 # 线程锁 lock = threading.Lock() def task(): global num # # 申请锁 # lock.acquire() # num += 1 # time.sleep(0.2) # print(num) # # 释放锁 # lock.release() with lock: num += 1 time.sleep(0.2) print(num) for i in range(10): t = threading.Thread(target=task) t.start()
-
RLock,递归锁支持上多次锁
import threading import time num = 0 # 线程锁 lock = threading.RLock() def task(): global num # 申请锁 lock.acquire() num += 1 lock.acquire() time.sleep(0.2) print(num) # 释放锁 lock.release() lock.release() for i in range(10): t = threading.Thread(target=task) t.start()
练习题: 基于线程锁完成一个单例模式.
import threading
import time
class Singleton:
instance = None
lock = threading.RLock()
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if cls.instance:
return cls.instance
with cls.lock:
if cls.instance:
return cls.instance
time.sleep(0.1)
cls.instance = object.__new__(cls)
return cls.instance
def task():
obj = Singleton('x')
print(obj)
for i in range(10):
t = threading.Thread(target=task)
t.start()
# 执行1000行代码
data = Singleton('asdfasdf')
print(data)
3.5 GIL
GIL,全局解释器锁.
同一时刻保证一个进程中只有一个线程可以被cpu调度,所以在使用Python开发时要注意:
计算密集型,用多进程.
IO密集型,用多线程.
- Python中如果创建多现场无法应用计算机的多核优势.
4.重点总结
- 初识爬虫
- 单例模式 ,重点面试 (需要默写)
- 为什么要加锁?
- 为什么要做判断?
- 进程和线程的区别? ,重点.面试
- GIL锁, 重点面试
- 线程的常用功能: start/join , 重点
python
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· SpringCloud带你走进微服务的世界