1.进程
定义:一个正在运行的应用程序就是一个进程。一个进程是运行在其专用而且受保护的内存空间中。
线程:一个进程要执行任务必须要有线程。
进程-车间: 线程——车间工人
线程特点:一个线程执行多个任务,是串行执行。
多线程:一个进程中有多个线程。多线程可以并行(同时)执行多个任务
多线程原理:多线程技术是通过利用CPU空闲时间干活来提高程序执行效率
2.多线程
一个应用程序默认对应一个进程,这个进程(主进程)中默认有一个线程(主线程)
from threading import Thread from multiprocessing import Process """ 1)直接使用Thread类 线程对象 = Thread(target=函数, args=参数对应的元组) 线程对象.start() 线程对象.join() 2)使用Thread类的子类 class 线程子类(Thread): def __init__(self): super().__init__() 实现线程任务需要的额外数据对应的属性 def run(self): 线程任务 线程对象 = 线程子类() 线程对象.start() 线程对象.join() 3)进程 进程对象 = Process(target=函数, args=参数对应的元组) 进程对象.start() 进程对象.join() """
3.线程队列
queue模块中的队列,只能保存一般数据或者多线程中产生的数据(多用于多线程,自带线程安全属性),但是不能用来存储多进程中产生的数据。
队列数据结构:是容器,先进先出from queue import Queue if __name__ == '__main__': # 1. 队列基本用法 # 1) 创建队列对象: Queue() q = Queue() # 2) 添加数据(进): 队列对象.put(数据) q.put(100) q.put(200) # 3) 获取数据(出) - 出一个就少一个: 队列对象.get() print('个数:', q.qsize()) print(q.get()) print('个数:', q.qsize()) print(q.get()) print('个数:', q.qsize()) # 4) 获取队列中元素的个数: 队列对象.qsize() # 5) 通过get获取数据的时候如果队列中没有数据,get方法会等待,直到队列中有数据,或者超时为止 # 队列对象.get(timeout=超时时间) # q.get()
4.队列在多线程中用法
from threading import Thread from queue import Queue, Empty import time from random import randint def download(name): print(f'{name}开始下载') time.sleep(randint(2, 9)) print(f'{name}下载结束') q.put(f'{name}数据') def del_data(): while True: data = q.get() if data == 'end': break print(f'------------处理{data}------------') if __name__ == '__main__': q = Queue() # 创建一个线程处理数据(方式3对应的代码) del_t = Thread(target=del_data) del_t.start() # 创建子线程下载数据 names = [f'电影{x}' for x in range(1, 11)] ts = [] # 保存下载电影的所有线程对象 for name in names: t = Thread(target=download, args=(name,)) t.start() ts.append(t) # 1. 获取队列数据方式1:能做到子线程得到数据,主线程马上就处理数据,但是数据处理完程序没法结束 # while True: # data = q.get() # print(f'------------处理{data}------------') # 2. 获取队列数据方式2: 能做到子线程得到数据,主线程马上就处理数据,通过是否超时来盘数数据是否处理完成 # while True: # try: # data = q.get(timeout=5) # print(f'------------处理{data}------------') # except Empty: # break # 3. 在所有下载数据的线程都结束的时候在队列中添加结束标记,在子线程中去获取队列中来处理 for t in ts: t.join() q.put('end')
5.进程队列
from multiprocessing import Process, Queue, current_process import time from random import randint from threading import Thread # from queue import Queue # 进程队列 """ 1)基本操作: 创建队列对象:Queue() 添加数据: 队列对象.put(数据) 获取数据: 队列对象.get() / 队列对象.get(timeout=超时时间) 2)注意事项: 如果想要使用一个队列对象获取不同进程中的数据,这个队列必须通过关联进程的函数的参数传递到进程中 """ def download(name, q:Queue): print(f'{name}开始下载') time.sleep(randint(2, 9)) print(f'{name}下载结束') q.put(f'{name}数据') def del_data(q: Queue): while True: data = q.get() if data == 'end': break print(f'--------------处理{data}----------------') if __name__ == '__main__': # 2. 在子进程中处理数据 # 2.1 创建队列对象,并且传递到子进程中 queue = Queue() del_p = Process(target=del_data, args=(queue,)) del_p.start() # 1. 下载电影 names = [f'电影{x}' for x in range(1, 11)] ps = [] for name in names: p = Process(target=download, args=(name, queue)) p.start() ps.append(p) # 2.2 等到所有的进程任务都结束在队列添加结束标记 for p in ps: p.join() queue.put('end') # 练习:使用多个进程同时下载多个电影,将下载的电影数据保存到队列中,在一个新的进程中去处理下载到的数据 # 做到:边下载边处理,下载完就处理完,处理完程序马上结束
6.线程池
from concurrent.futures import ThreadPoolExecutor import time from random import randint from threading import current_thread from datetime import datetime def download(name): print(f'{name}开始下载') print(current_thread()) time.sleep(1) print(f'{name}下载结束') # 线程池的工作原理:提前创建指定个数的线程,保存到一个线程池中。 # 然后再往线程池中添加若干个任务,线程池自动为线程分配任务。 # 1. 创建线程池,确定线程池中线程的个数 pool = ThreadPoolExecutor(max_workers=50) # 2. 往线程池中添加添加任务 names = [f'电影{x}' for x in range(100)] # 1) 一次添加一个任务 # 线程池.submit(函数, 参数1, 参数2,...) # pool.submit(download, '电影0') # print('开始时间:', datetime.now()) # for name in names: # pool.submit(download, name) # 2) 一次添加多个任务 # 线程池.map(函数, 包含所有任务的参数的序列) pool.map(download, names) pool.submit(download, 'hello!') # 3.关闭线程池 # 线程池.shutdown() - 关闭线程池以后,线程池无法再添加任务,但是不影响已经添加的任务的执行 pool.shutdown() # pool.submit(download, '电影100')
7.线程池使用的细节
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, as_completed import time from random import randint def download(name, x): print(f'{name}-{x}开始下载') time.sleep(randint(1, 4)) print(f'{name}-{x}下载结束') return f'{name}数据' if __name__ == '__main__': # 1.创建线程池 pool = ThreadPoolExecutor(max_workers=30) # 2.添加任务 # 线程池.submit(函数) - 函数可以是有任意多个参数的函数; 返回值是一个可操作的future对象(任务对象) # 线程池.map(函数) - 函数只能是有且只有一个参数的函数; 返回值没法控制和操作 f = pool.submit(download, '电影1', 10) all_task = [pool.submit(download, f'电影{x}', x*10) for x in range(100)] # all_task = [] # for x in range(100): # f = pool.submit(download, f'电影{x}', x*10) # all_task.append(f) # 3.等待任务完成 # wait(all_task, return_when=ALL_COMPLETED) # print('--------------------电影下载完成--------------------') # 4. 获取任务函数的返回值 # 按照任务完成的先后顺序获取任务,并且获取已经完成的任务的结果(函数返回值) for task in as_completed(all_task): print(f'------------{task.result()}------------------') print('--------------------电影下载完成--------------------')
8.进程池
from multiprocessing import Pool import time from random import randint def download(name): print(f'{name}开始下载') time.sleep(randint(1, 4)) print(f'{name}下载结束') return f'{name}数据' if __name__ == '__main__': # 1. 创建进程池对象: Pool(进程数) pool = Pool(10) # 2. 添加任务 # 1) 一次添加一个任务 """ a. 进程池对象.apply(函数, 参数) - 同步(串行);进程池中的多个任务串行执行 b. 进程池对象.apply_async(函数, 参数) - 异步执行(并行);必须配合close和join一起用 函数 - 任务对应的函数的函数名 参数 - 元组;调用任务函数的时候的实参,需要多少个实参,元组中就给多少个元素 """ # for x in range(20): # pool.apply_async(download, (f'电影{x}',)) # 2) 同时添加多个任务 """ 进程池.map(函数, 参数序列) - 序列中有多少个元素就添加多少个任务; 进程池中的任务并行, 进程池中的任务和主进程串行 进程池.map_async(函数, 参数序列) - 进程池中的任务和主进程并行执行 map和map_async的返回值是所有任务对应的函数的返回值 """ result = pool.map_async(download, [(f'电影{x}', x*10) for x in range(20)]) print('--------------主进程-------------------') # 3. 关闭进程池,阻止往进程池中添加新的任务 pool.close() # 4.等待进程池中的任务都结束 pool.join() print('---------------下载完成--------------------') # 获取函数返回值 print(result.get())
9.线程池51job
import requests from re import findall from json import loads from concurrent.futures import ThreadPoolExecutor, as_completed, wait import csv def get_one_page(page): headers = { 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36' } url = f'https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,{page}.html?lang=c&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=' response = requests.get(url, headers=headers) json_data = findall(r'window.__SEARCH_RESULT__ =(.+?)</script>', response.text)[0] dict_data = loads(json_data) page_data = [] for job in dict_data['engine_search_result']: page_data.append({ '工作名称': job['job_name'], '工作详情': job['job_href'], '薪资': job['providesalary_text'], '工作地点': job['workarea_text'], '工作年限': job['workyear'], '福利待遇': job['jobwelf'], '公司': job['company_name'] }) # print(page_data) return page_data if __name__ == '__main__': # 1. 使用线程池下载数据 pool = ThreadPoolExecutor(20) all_task = [pool.submit(get_one_page, page) for page in range(1, 101)] # 2. 在主线程处理数据 f = open('files/python.csv', 'a', encoding='utf-8') writer = csv.DictWriter(f, ['工作名称', '工作详情', '薪资', '工作地点', '工作年限', '福利待遇', '公司']) writer.writeheader() for t in as_completed(all_task): print('----------处理一页数据---------') writer.writerows(t.result())