首页 > 技术文章 > 并发编程—线程

WM2019 2019-11-03 22:04 原文

1、线程基础

1.1 什么是线程

操作系统中能够独立运行的基本单位

1.2 线程的出现

为了弥补线程的缺陷,引入了线程

进程缺陷:

1、进程通过一时间只能左一件事

2、若遇到IO,只能挂起/

硬件条件:

多对称处理机(SMP),可以满足多个运行单位

1.3 使用线程的好处

开启进程:开辟一个内存空间,占用内存资源,自带一个线程

开启线程:一个进程可以开启多个线程,线程的开销远小于进程

1.4 守护线程

2、线程创建与执行

1、自定义任务

import time
from threading import Thread

def task():
    print('线程开启')
    time.sleep(1)
    print('线程结束')

# t = Thread()  # 线程对象可以放在main外面实例化,但是进程对象不行

if __name__ == '__main__':
    t = Thread(target=task)
    t.start()

2、自定义类

import time
from threading import Thread

class MyThread(Thread):
    def run(self):
        print('线程开启')
        time.sleep(1)
        print('线程结束')

t = MyThread()
t.start()

3、多线程

3.1 什么是多线程

多个线程同时运行,需要注意的是,多线程只能并发,执行顺序与启动顺序无关

实现方法:

import time
from threading import Thread

def task():
    print(f'任务执行')
    
for i in range(10):
    t = Thread(target=task)
    t.start()

3.2 线程池

python标准模块concurrent.futures.md

3.3 多线程应用实例:爬取梨视频

import requests
import re
from concurrent.futures import ThreadPoolExecutor
import uuid

pool = ThreadPoolExecutor(100)

# 主页
URL=r'https://www.pearvideo.com/'

def get_page(url):
    '''发送请求,获取网页信息'''
    response = requests.get(url)
    return response

def get_vedio_page_url_list(response):
    '''提取网页信息,获取所有视频信息页url'''
    re_format = '<a href="video_(.*?)".*?>'
    res = re.findall(re_format,response,re.S)
    for i in range(len(res)):
        res[i] = 'https://www.pearvideo.com/video_' + res[i]
    url_list = res
    return url_list

def get_video_data_url_list(video_page_url_list):
    '''通过视频信息也链接,发送请求并提取所有视频数据url'''
    video_data_url_list = []
    for url in video_page_url_list:
        response = get_page(url)
        print(response)
        re_format = 'srcUrl="(.*?)"'
        video_data_url = re.findall(re_format,response.text,re.S)[0]
        print(video_data_url)
        video_data_url_list.append(video_data_url)

    return video_data_url_list

def download(res):
    '''提取视频二进制数据,保存到本地'''
    res = res.result()  # 作为回调函数时使用

    name = str(uuid.uuid4())
    print(f'{name}.mp4视频开始保存...')
    with open(f'{name}.mp4','wb') as fw:
        fw.write(res.content)

if __name__ == '__main__':
    # 访问主页
    res = get_page(URL)
    
    #提取所有的视频详情页链接url
    video_page_url_list = get_vedio_page_url_list(res.text)
    
    # 提取所有的视频数据链接url
    video_data_url_list = get_video_data_url_list(video_page_url_list)
    
    # 多线程下载视频保存到本地
    for url in video_data_url_list:
        movie_data_url = pool.submit(get_page,url).add_done_callback(download)

4、线程间通信

4.1 线程数据

进程是操作系统最小的资源单位,进程下可以开启多个线程

因此线程之间数据是共享的

4.2 什么是线程间通信

同一进程下,由于多个线程处理同一资源,但是各线程的执行任务任务不同

4.3 生产者与消费者

4.3.1 什么是生产者、消费者

生产者:产生数据的线程

消费者:使用数据的线程

4.3.1 单生产单消费

一个生产者和一个消费者

4.3.2 多生产多消费

多个生产者和多个消费者

代码:

import time
import random
from concurrent.futures import ThreadPoolExecutor

p = ThreadPoolExecutor(10)

num_list = []


# 生产者任务
def create_data(i):
    while True:
        if len(num_list) >=10:
            time.sleep(1)
            continue
        num = random.random()
        num_list.append(num)
        print(f'{i}生产了数字:{num}')
        time.sleep(1)



# 消费者任务
def get_data(i):
    while True:
        if not len(num_list):
            continue
        num = num_list[-1]
        del num_list[-1]
        print(f'{i}消耗掉了数字:{num}')

# 创建单生产者与单消费者
# p.submit(create_data,1)
# p.submit(get_data,2)


# 创建多生产者与多消费者
for i in range(10):
    p.submit(create_data, i)
    p.submit(get_data,i)

5、线程互斥锁

互斥锁

推荐阅读