爬虫专题python基础Python中文社区

Python 线程、线程通信、多线程

2018-09-18  本文已影响97人  梅花鹿数据rieuse

这是一篇学习Python 线程相关的内容,记录一下以备复习和开发使用,技术有限,如有问题欢迎指出,多谢。


一.GIL 全局解释器锁(cpython)

1.为什么会有这个锁:为了线程安全,减少python使用者的上手难度
GIL 使得同一个时刻只有一个线程在一个cpu上执行字节码,无法隐射到多个cpu,多核上执行。
2.特殊情况下会释放GIL:达到特定字节码行数、到底特定数目时间片、IO操作(主动)

二:并发和并行的区别
三:守护线程&线程阻塞
四:多线程的写法
五:线程间通信
q = Queue()
# 方法:
q.put()  # 放入数据
q.get()  # 取出数据
q.put_nowait()  # 放入数据,不用等待它完成再返回,异步的方法
q.get_nowait()  # 取出数据,不用等待它完成再返回,异步的方法

get() put(),可以设置是否阻塞的,默认是阻塞的

q.join()方法:
只有q.task_done()调用了join()才会让主线程退出,成对使用。

六:线程同步
lock= Theading.Lock()
# 获取锁:
lock.acquire()
lock.release()

# 另一种方法:
with lock:
    # do something

加锁的代码段同时只有这一个代码段在执行,方式数据出问题。
缺点:1.用锁会影响性能 2. 可能引起死锁
死锁情况:1.有acquire 没有release 2. 相互等待

con = theading.Condition()
with con:
    # do something
    cond.notify()   #通知其他线程
    cond.wait()    # 等待其他线程通知
    # do something

注意:
1.先con.acquire()或者with con,获取condition锁,不然wait() notify() 不能用
2.Condition 有两把锁:一把底层锁会在线程调用了wait() 的时候释放,上面的锁会在每次调用wait的时候分配一把并放入condition的等待队列中,等到notify()的唤醒

import threading
import time


class HtmlSppier(threading.Thread):
    def __init__(self, url, sem):
        super().__init__()
        self.sem = sem
        self.url = url

    def run(self):
        time.sleep(2)
        print('download html success')
        self.sem.release()

class UrlProducer(threading.Thread):
    def __init__(self,sem):
        super().__init__()
        self.sem = sem


    def run(self):
        for i in range(20):
            self.sem.acquire()
            html_thread = HtmlSppier(f'http://www.qq.com/pn={i}',self.sem)
            html_thread.start()


if __name__ == '__main__':
    sem = threading.Semaphore(3)
    url_produce = UrlProducer(sem)
    url_produce.start()
七:线程池

为什么要使用线程池?
主线程中可以获取某一个线程的状态或者某一个的任务的状态以及返回值
当一个线程完成的时候主线程能立即知道

import requests

def download_html(i):
    url = f'https://www.baidu.com/s?ie=UTF-8&wd={i}'
    response = requests.get(url).text
    print(response)

ids = list(range(100))


# 线程池方式一:
import threadpool
def thread_main(item):
    pool = threadpool.ThreadPool(30)
    tasks = threadpool.makeRequests(download_html, ids)
    [pool.putRequest(req) for req in tasks]
    pool.wait()


# 线程池方式二:
from multiprocessing.dummy import   Pool as thpool

def thread_pool(item):
    pool = thpool(20)
    pool.map(download_html, ids)
    pool.close()
    pool.join()


# 线程池方式三(推荐):
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=8) as exe:
    exe.map(download_html,ids)

推荐使用 concurrent.futures 模块,线程池和进程池的接口很相似,方便使用。

ThreadPoolExecutor 其他方法使用:
# 其他接口使用:
from concurrent.futures import ThreadPoolExecutor, as_completed,wait


executor = ThreadPoolExecutor(max_workers=8)

# 通过 submit 提交执行的函数到线程中
task1 = executor.submit(download_html, (1))
task2 = executor.submit(download_html, (3))

# done() 判断 task 是否完成
print(task1.done())
time.sleep(4)
print(task1.done())

# result() 获取 task 的执行结果 阻塞
print(task1.result())

# cancel() 取消任务,如果任务在执行中或者执行完了是不能取消的
# 现在线程池是8 两个任务都会被提交任务去执行,如果 max_workers = 1,执行task2.cancel()就会成功取消
print(task2.cancel())


# as_completed() 获取已经成功的task的返回数据,阻塞
# as_completed实际上是一个生成器,里面有 yield 会把已经完成的 future (task) 返回结果
ids = list(range(10))
all_task = [executor.submit(download_html,(i)) for i in ids]
time.sleep(8)
# 这是异步的,谁完成就处理谁
for future in as_completed(all_task):
    data = future.result()
    print(f'html response {data}')


# 通过 executor 获取已经完成的task
for data in executor.map(download_html,ids):
    print(f'html response {data}')


# wait() 等待task完成
ids = list(range(10))
all_task = [executor.submit(download_html,(i)) for i in ids]

#  wait 的 return_when 可选项
FIRST_COMPLETED = 'FIRST_COMPLETED'
FIRST_EXCEPTION = 'FIRST_EXCEPTION'
ALL_COMPLETED = 'ALL_COMPLETED'
_AS_COMPLETED = '_AS_COMPLETED'

wait(all_task, return_when=ALL_COMPLETED)
八:总结

Python 多线程首选concurrent.futures 中的 ThreadPoolExecutor,使用简单方便,而且切换多进程也是很快速的,后面继续记录多进程方面的知识点。
代码位置:github.com/rieuse/learnPython

上一篇下一篇

猜你喜欢

热点阅读