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
线程间的通讯方式
模仿爬虫的逻辑 写一段说明线程共享全局变量的代码:
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
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