Bootstrap

10.多线程与多进程编程

1. 全局解释器锁 GIL

1. gil  全称 global interpreter lock
2. python 中一个线程 对应 c语言中一个线程
3. gil使得同一时刻只有一个线程在cpu上执行字节码,无法将多个线程映射到多个cpu上
4. gil会根据执行的字节码行数以及时间片 释放gil
5. gil在遇到io操作的时候会主动释放
import threading
total = 0


def add():
    # 1. dosomething1
    # 2. io操作
    # 3. dosomething3
    global total
    for i in range(1000000):
        t = total + 1
        total = t


def desc():
    global total
    for i in range(1000000):
        t = total - 1
        total = t


thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()


thread1.join()
thread2.join()
print(total)

每次运行结果都会不同:
-147368
-13294
-180734
-59620
306061
原因就是:gil会根据执行的字节码行数以及时间片 释放gil, 比如:在add函数total = t的时候, 可能给total赋的值是desc函数total =t 的结果, 这样 total的结果始终是错乱的。


2. 多线程编程threading

函数的方式使用多线程:

有两个需求一并实现:

1.  主线程退出时, 子线程会被kill掉 
2.  主线程等待子线程执行完成
import time
import threading

# 动态建立thread  线程池  函数的方法适用


def get_detail_html(url):
    print('get detail html started')
    time.sleep(2)
    print('get detail html end')


def get_detail_url(url):
    print('get detail url started')
    time.sleep(2)
    print('get detail url end')


if __name__ == '__main__':
    thread1 = threading.Thread(target=get_detail_html, args=('',))
    thread2 = threading.Thread(target=get_detail_url, args=('',))
    thread1.setDaemon(True)  # 主线程退出时, 子线程kill    setDaemon设置守护线程
    thread2.setDaemon(True)
    start_time = time.time()
    thread1.start()
    thread2.start()

    thread1.join()   # 主线程阻塞, 直到子线程执行完成
    thread2.join()

    print('last time: {}'.format(time.time()-start_time))  # 打印子线程执行所用时间

结果为:

get detail html started
get detail url started
get detail html end
get detail url end
last time: 2.0045289993286133
类继承的方式使用多线程:

代码:

import time
import threading

# 大部分情况使用   适合复杂逻辑
class GetDetailHtml(threading.Thread):
    def __init__(self, name):
        super().__init__(name=name)

    def run(self):
        print('get detail html started')
        time.sleep(2)
        print('get detail html end')


class GetDetailUrl(threading.Thread):
    def __init__(self, name):
        super().__init__(name=name)

    def run(self):
        print('get detail url started')
        time.sleep(2)
        print('get detail url end')


if __name__ == '__main__':
    thread1 = GetDetailHtml('get detail html')
    thread2 = GetDetailUrl('get detail url')
    thread1.setDaemon(True)  # 主线程退出时, 子线程kill    setDaemon设置守护线程
    thread2.setDaemon(True)
    start_time = time.time()
    thread1.start()
    thread2.start()

    thread1.join()   # 主线程阻塞, 直到子线程执行完成
    thread2.join()

    print('last time: {}'.format(time.time()-start_time))

适用情形:
'函数形式的多线程编码':   动态建立thread 或 线程池  的时候适用
'类继承形式的多线程编码':  大部分情况使用   特别适合复杂逻辑

3. 线程间通信 - 共享变量和Queue

线程间的通讯方式

  1. 共享全局变量方式

模仿爬虫的逻辑 写一段说明线程共享全局变量的代码:

import time
import threading

detail_url_list = []


def get_detail_html(detail_url_list):
    # 爬取网站详情页
    while True:
        if len(detail_url_list) > 0:
            url = detail_url_list.pop()   # 取出存好url
            print('get detail html started: {}'.format(url))
            time.sleep(2)
            print('get detail html end')


def get_detail_url(detail_url_list):
    # 爬取网站的文章列表
    while True:
        for i in range(20):    # 生成url
            detail_url_list.append('http://projectsedu.com/{id}'.format(id=1))
        print('get detail url started')
        time.sleep(2)
        print('get detail url end')


if __name__ == '__main__':
    thread_detail_url1 = threading.Thread(
        target=get_detail_url, args=(detail_url_list,))
    for i in range(20):    # 每一个生成的url  都对应一个get_detail_html线程来处理
        html_thread = threading.Thread(
            target=get_detail_html, args=(detail_url_list,))
        html_thread.start()     
    thread_detail_url1.start()  # 开始生成url

  1. 通过queue的方式进行线程同步
queue的所有操作都是线程安全的

依然以爬虫逻辑来写一段代码 对queue的简单使用举例:

from queue import Queue

import time
import threading


def get_detail_html(queue):
    while True:
        # 爬取网站详情页
        url = queue.get()   # queue如果为空  会阻塞在这里
        print('get detail html started: {}'.format(url))
        time.sleep(2)
        print('get detail html end')
        # queue的角度阻塞 主线程, 每次queue.get  后的逻辑运行完,就写一下 task_done
        thread_detail_queue.task_done()


def get_detail_url(queue):
    # 爬取网站的文章列表

    for i in range(20):  # 出来一个url  就开一个get_detail_html线程来处理
        queue.put('http://projectsedu.com/{id}'.format(id=i))
    print('get detail url started')
    time.sleep(2)
    print('get detail url end')


if __name__ == '__main__':
    thread_detail_queue = Queue(maxsize=1000)
    thread_detail_url1 = threading.Thread(
        target=get_detail_url, args=(thread_detail_queue,))
    thread_detail_url1.setDaemon(True)   # 主线程结束  子线程结束
    for i in range(20):
        html_thread = threading.Thread(
            target=get_detail_html, args=(thread_detail_queue,))
        html_thread.setDaemon(True)     # 主线程结束  子线程结束
        html_thread.start()
    
    thread_detail_url1.start()

    start_time = time.time()

    thread_deta
;