17-多线程
GIL
- global interpreter lock 全局解释器锁
- 作用
- 保证只有一个线程执行
- 因为有GIL的存在,多个线程也只会占用一个CPU
- 进程会使用多个CPU
创建线程
-
线程:主线程/分线程
-
方法1
-
# 线程是CPU分配资源的基本单位。一个程序开始运行,就变成了一个进程,而一个进程相当于一个或者多个线程。当没有多线程编程时,一个进程也是一个主线程,当有多线程编程时,一个进程包含多个线程(包括主线程)。使用线程可以实现程序的并发。 import _thread import time import threading # 第一种方式 # 子线程/分线程 def thread1(*args): print('线程1:', args) print('子线程', threading.current_thread().name) # 子线程 Dummy-1 def creat_thread1(): # 当前线程名 # MainThread print('主线程', threading.current_thread().name) # 创建子线程 # 守护线程:子线程会随着主线程的结束而结束 # 主公就是主线程,忠臣就是子线程 _thread.start_new_thread(thread1, ('韩信', '刘邦')) time.sleep(20) # 第二种方式 def thread2(*args): print('子线程:', args) print('子线程:', threading.current_thread().name) # 子线程: 线程1 def create_thread2(): t = threading.Thread(target=thread2, name='线程1', args=('李白', '杜甫')) t.start() # 启动线程 print('hello') # 第三种方式 # 自定义线程 # 继承 class MyThread(threading.Thread): def __init__(self, url): super().__init__() self.url = url # 重写run方法:会自动调用,这个函数就是子线程的函数,类似target def run(self): print('子线程:', threading.current_thread().name) # 子线程: Thread-1 def create_thread3(): t = MyThread('http://www.baidu.com') t.start() if __name__ == '__main__': # creat_thread1() # create_thread2() create_thread3()
-
多线程
-
同步:在一个线程上执行
-
异步:在不同的线程上执行,在异步的基础上加上t.join()(阻塞),即变成同步
-
# 等待所有线程全部执行完 for t in t_list: t.join() # 阻塞,等到最后一个线程结束
-
-
Python的多线程是假的多线程,但是仍然会提高效率
-
线程的其他属性和方法
- print(t.name, t.getName()) # 当前线程的名字
- print(t.isDaemon()) # 是否为守护线程
- print(t.ident) # 线程号
- print(t.is_alive()) # 线程是否在运行
- print(threading.active_count()) # 正在运行的线程数量
- print(threading.enumerate()) # 列举所有正在运行的线程
cpu密集型(计算密集型)&I/O密集型
-
计算密集型任务由于主要消耗CPU资源,代码运行效率至关重要,C语言编写;
- 计算密集型:建议使用多进程(多进程可以使用到多核)
-
IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部时间都在等待IO操作完成,99%的时间花费在IO上,脚本语言是首选,C语言最差。
- IO密集型:Input Output(一般是比较耗时的操作),建议使用多线程。
-
多线程爬虫
-
# 使用多线程爬取深圳所有区的房源,并分别保存到单独的以区为文件名的html中,如: 南山区.html # (爬取每个区的第一页即可) # https://sz.lianjia.com/ershoufang/pg1/ import re import threading import requests # 模拟浏览器 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" } # 获取所有区 def get_area(): url = 'https://sz.lianjia.com/ershoufang/pg1/' # 获取网页数据 response = requests.get(url, headers=headers) content = response.text # print(content) # 获取所有区 pattern = r'data-role="ershoufang"(.*?)</div>' area_str = re.findall(pattern, content, re.S)[0] print(area_str) pattern2 = r'href="(.*?)"' area_url_list = re.findall(pattern2, area_str, re.S) # print(area_url_list) pattern3 = r'">(.*?)</a>' area_name_list = re.findall(pattern3, area_str, re.S) # print(area_name_list) return area_url_list, area_name_list # 子线程 def get_data(url, name): # 每个区的url网址 url = 'https://sz.lianjia.com' + url response = requests.get(url, headers=headers) with open(f'lianjia/{name}.html', 'wb') as fp: fp.write(response.content) # 写入二进制 if __name__ == '__main__': # 获取所有区 area_url_list, area_name_list = get_area() for i in range(len(area_url_list)): name = area_name_list[i] url = area_url_list[i] t = threading.Thread(target=get_data, args=(url, name)) # 注意传入变量的顺序 t.start()
-
线程冲突即线程锁
-
线程冲突
-
n = 0 def f(): global n for _ in range(1000000): n += 1 print(n) def create_thread(): # 同时开启5个线程,同时访问同一个变量n for _ in range(5): t = threading.Thread(target=f) t.start() create_thread()
-
多个线程同时操作一个资源时,可能造成资源混乱
-
-
线程锁/互斥锁
-
解决线程冲突
-
自动加锁,用完后自动解锁,加锁过程种其他线程无法访问
-
对资源加锁
-
import threading # 线程冲突:多个线程同时操作一个资源时,可能造成资源混乱 # 线程锁/互斥锁:解决线程冲突 # 死锁现象:多个线程同时操作多个资源时 # 比如: # 线程1 线程2 # A B # 递归锁/重用锁:解决死锁 n = 0 def f(): global n for _ in range(1000000): n += 1 print(n) # 线程冲突 def create_thread(): # 同时开启5个线程,同时访问同一个变量n for _ in range(5): t = threading.Thread(target=f) t.start() # t.join() # 同步 # 线程锁:解决线程冲突 lock = threading.Lock() # 递归锁:解决死锁 # rlock = threading.RLock() def f2(): # 自动加锁,用完后会自动解锁 # 加锁过程种其他线程无法访问 # with lock: # with rlock: # 递归锁的使用 # global n # for _ in range(1000000): # n += 1 # print(n) lock.acquire() # 加锁 global n for _ in range(1000000): n += 1 print(n) lock.release() # 解锁 # 线程冲突 def create_thread2(): # 同时开启5个线程,同时访问同一个变量n for _ in range(5): t = threading.Thread(target=f2) t.start() if __name__ == '__main__': # 线程冲突 # create_thread() # 线程锁 create_thread2()
-
-
-
信号量
-
# 信号量 import random import threading # 信号量:控制线程的最大并发数,每次最多5个线程同时执行 import time sem = threading.Semaphore(5) def fn(*args): with sem: print('子线程:', args) time.sleep(3+random.random()) if __name__ == '__main__': for i in range(1, 21): threading.Thread(target=fn, args=(f'Thread-{i}',)).start()