【笔记】2022.5.18 多线程

本文详细介绍了Python多线程的基本概念,如进程与线程的区别,以及多线程的串行和并行用法。涵盖了实际案例中如何利用阻塞和队列进行通信,以及线程池的创建、任务提交和错误处理。最后展示了如何使用线程池实现任务重试和结束标志管理。
摘要由CSDN通过智能技术生成

1. 基本概念

(1)进程:一个正在运行的应用程序就是一个进程。每个进程均运行在其专用且受保护的内存空间中。(进程结束,内存会自动释放)。进程相当于工厂。

(2)线程:是进程执行任务的基本单元。如果一个进程需要执行任务,必须要线程。所有的任务都必须在线程中执行。线程相当于工人。

(3)线程的串行:如果在一个线程中执行多个任务,多个任务是串行执行的(一个一个按顺序执行的)。

(4)多线程:一个进程中默认只有一个线程。多线程就是一个进程中同时拥有多个线程。

特点:多个线程执行多个任务,是并行(同时)执行的。

多线程可以提高程序的执行效率,但是并不是线程数越多越好。一般情况下:手机应用程序一般3-5个、电脑程序几十个到两三百个

(5)多线程原理:多线程的存在可以让一个程序同一时间执行不同的任务。一个cpu同一时间只能执行一个线程。多线程实质上是CPU在多个线程之间来回调度(切换),当调度速度足够快的时候,就会造成多线程同时执行的假象。类似这样:

img


2. 多线程基本用法

python程序默认只有一个线程(这个线程叫主线程),除了主线程以外需要别的线程(子线程),必须用代码来创建线程对象(Thread的对象)。默认情况下,程序中的代码都是在主线程中执行。

(1)导入线程类

RIGHT Example:

from threading import Thread
from datetime import datetime
from time import sleep


def download(m_url):
    print(f'---------{m_url}开始下载:{datetime.now()}--------------')
    sleep(2)
    print(f'---------{m_url}下载结束:{datetime.now()}--------------')

(2)线程的串行

RIGHT Example:

download('肖申克的救赎')
download('许三观卖血记')
download('沉默的羔羊')

(3)多线程的并行

RIGHT Example:

# (1)创建线程对象,并且给子线程添加任务
"""
Thread(target=函数, args=元组)
函数:需要在子线程中执行的任务
元组:元组中的元素就是target对应的函数在调用的时候需要的参数
"""
t1 = Thread(target=download, args=('肖申克的救赎',))
t2 = Thread(target=download, args=('许三观卖血记',))
t3 = Thread(target=download, args=('沉默的羔羊',))

# (2)启动线程:线程对象.start()
t1.start()
t2.start()
t3.start()

(4)批量创建子线程

RIGHT Example:

names = [f'电影{x}' for x in range(1, 11)]
for x in names:
    t = Thread(target=download, args=(x,))
    t.start()

3. 实际案例

*APPLICATION 使用多线程获取数据:

import requests
import csv
from datetime import datetime
from tqdm import tqdm
from lxml import etree
from threading import Thread

headers = {
    'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36'
}


def get_resp(url):
    resp = requests.get(url=url, headers=headers)
    return resp


def get_all(text, xpath):
    root = etree.HTML(text)
    return root.xpath(xpath)


def get_one(single, xpath):
    target = single.xpath(xpath)
    return target


def main(i):
    print(f'开始下载{datetime.now()}')
    url = f'https://movie.douban.com/top250?start={i}&filter='
    resp0 = get_resp(url)
    html_list = get_all(resp0.text, '//div[@class="article"]/ol[@class="grid_view"]/li')

    for single in tqdm(html_list):
        name = get_one(single, './div/div[@class="info"]/div/a/span[1]/text()')
        writer.writerow(name)

    print(f'结束下载{datetime.now()}')


if __name__ == '__main__':
    writer = csv.writer(open(f'files/top250.csv', 'w', encoding='utf-8', newline=''))

    for i in range(0, 226, 25):
        t = Thread(target=main, args=(i,))
        t.start()

4. 阻塞

多线程编程的时候,如果一个线程中需要等待另外的子线程中的任务结束才执行某个操作,就可以在需要等待的位置用子线程对象调用join方法

注意:如果只需要等待1个子线程任务完成,就用一个子线程对象调用join,如果需要等多个子线程任务完成,就用多个线程对象调用join

RIGHT Example:

# 案例1:等待所有的子线程任务都结束,打印全部下载完成
t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()

print('全部下载完成')

# 案例2:第1个电影下完才同时下第2个和第3个电影
t1.start()
t1.join()
t2.start()
t3.start()

t2.join()
t3.join()

print('全部下载完成')

# 案例3:同时下载10个电影,要求10个电影都下载结束的时候打印'全部下载完成'

all = []
for i in range(1, 11):
    t = Thread(target=download, args=(f'电影{i}',))
    all.append(t)
    t.start()

for i in all:
    i.join()

print('下载完成')

5. 多线程通信

同一个进程中的多个线程之间可以直接通信

通信方法:使用全局变量

注意:如果子线程调用的函数有返回值,那么这个函数的返回值是没有办法获取的

WRONG Example:

def f(n: int):
    s = 1
    for x in range(1, n + 1):
        s *= x
    return s


if __name__ == '__main__':
    t1 = Thread(target=f, args=(10,))
    t2 = Thread(target=f, args=(34,))

    print(t1.start())	# None
    print(t2.start())	# None

RIGHT Example:

def f(n: int):
    s = 1
    for x in range(1, n + 1):
        s *= x
    result.append(s)


if __name__ == '__main__':
    result = []

    t1 = Thread(target=f, args=(10,))
    t2 = Thread(target=f, args=(34,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print(result)   # [3628800, 295232799039604140847618609643520000000]

6. 队列的使用

队列是容器,可以同时保存多个数据,数据保存的特点:先进先出

创建队列:Queue()

添加数据:队列对象.put(数据)

获取数据:队列对象.get()

get获取的是当前队列中最先添加的数据,获取一个数据就会少一个

当队列为空时,get操作不会报错,并且会阻塞线程,直到队列中有数据为止

6.1 基本用法

RIGHT Example:

def download(name):
    print(f'----------{name}开始下载:{datetime.now()}')
    time.sleep(random.randint(2, 7))
    print(f'----------{name}下载结束:{datetime.now()}')
    q1.put(f'{name}数据')


if __name__ == '__main__':
    q1 = Queue()

    for x in range(1, 6):
        t = Thread(target=download, args=(f'电影{x}',))
        t.start()

    for _ in range(5):
        print(f'保存:{q1.get()}')

6.2 添加结束标志

RIGHT Example:

from queue import Queue
from threading import Thread
import time, random
from datetime import datetime


def download(name):
    print(f'----------{name}开始下载:{datetime.now()}')
    time.sleep(random.randint(5, 10))
    print(f'----------{name}下载结束:{datetime.now()}')
    q1.put(f'{name}数据')


def save_data():
    while True:
        data = q1.get()
        if data == 'end':
            print('结束了')
            break
        else:
            print(f'开始保存{data}')
            time.sleep(random.randint(1, 5))
            print(f'保存{data}成功')


if __name__ == '__main__':
    q1 = Queue()

    ts = []
    for x in range(1, random.randint(3, 5)):
        t = Thread(target=download, args=(f'电影{x}',))
        t.start()
        ts.append(t)

    # 从队列中获取数据,处理数据的操作必须在子线程中执行
    t1 = Thread(target=save_data)
    t1.start()

    # 在所有下载数据的线程都结束以后,往队列中添加结束标志
    for t in ts:
        t.join()

    q1.put('end')

7. 线程池的使用


7.1 线程池相关概念

线程池:线程池用来保存线程并且给线程分配任务
工作原理:提前创建指定个数个线程,并且添加所有需要在子线程中执行的任务,然后线程池会自动给线程池中的线程分配提前准备好的任务


7.2 创建线程池

(1)创建线程池对象:ThreadPoolExecutor(线程数)

RIGHT Example:

pool = ThreadPoolExecutor(3)

(2)添加任务

a. 一次添加一个任务:线程池对象.submit(函数, 参数1, 参数2, …)

RIGHT Example:

pool.submit(download, '电影1')
pool.submit(download, '电影2')

b. 一次添加多个任务:线程池对象.map(函数, 列表)

注意:这儿的函数只能是只有一个参数的函数

RIGHT Example:

pool.map(download, ['电影3', '电影4', '电影5', '电影6', '电影7'])
pool.map(download, [f'电影{x}' for x in range(8, 21)])

c. 关闭和等待

关闭:如果关闭线程池,那么线程池就不能再添加任务

等待:等线程池中所有的任务都完成

RIGHT Example:

pool.shutdown()

7.3 案例:如果失败了就再添加回线程池

RIGHT Example:

import time, random
from queue import Queue
from concurrent.futures import ThreadPoolExecutor


def print_num():
    try:
        if random.randint(1, 3) == 1:
            raise KeyError
        print(1)
    except:
        print('失败了!')
        global future
        future = pool.submit(print_num)


if __name__ == '__main__':
    count_ = 0
    q = Queue()
    pool = ThreadPoolExecutor(4)

    for x in range(20):
        future = pool.submit(print_num)

    while not future.done():
        time.sleep(1)
Burp Suite 2022.6.1是Burp Suite的一个版本,它是一个用于Web应用程序渗透测试的工具。根据引用,您可以通过关注VX公众号401SecNote并回复"burp"来获取Burp Suite Professional v2022.6.1及其运行环境。此外,根据引用,您还可以通过百度网盘链接https://sysin.org/blog/burp-suite-pro-2022-6/下载Burp Suite Professional / Community 2022.6版本。如果您正在考虑安装高版本的Burp Suite,但担心与其他软件的兼容性问题,根据引用,您可以使用高版本的jdk来解决兼容性问题,并记录解决过程以备将来参考。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Burp Suite Professional 2022.6 (macOS, Linux, Windows) - Web 应用安全、测试和扫描](https://blog.csdn.net/netgc/article/details/125592616)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [笔记 | 在JAVA1.8环境下安装高版本Burp Suite Pro](https://blog.csdn.net/dust_hk/article/details/126489797)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sprite.Nym

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值