python使用多线程, 不一定运行速度快,这里引入GIL(global interpreter lock)
python解释器中任意时刻都只有一个线程在执行;
多线程的应用场景: I/O密集型(input, output) — 爬虫
不建议使用多线程的场景: 计算密集型(cpu一直占用)
1). 理论上多线程执行任务是不能获取返回结果的, 因此需要一个容器来存储产生的数据;
2). 容器该如何选择? list(栈, 队列), tuple(元组是不可变的, 不可使用),
set(集合默认会去重, 所以不选择), dict
选择队列类型存储(FIFO===first input first output)
例1:
import threading
from mytimeit import timeit #自己写的,附在下面
from queue import Queue
def job(li, queue):
queue.put(sum(li)) # 将任务的执行结果存储到队列中;
@timeit
def use_thread():
# 实例化一个队列, 用来存储每个线程执行的结果
q = Queue()
# q.get() -- 出队
# q.put(value) -- 入队
lis = [range(5), range(2,10), range(1000, 20000), range(3000, 10000)]
# create 5 threads
threads = []
for li in lis:
t = threading.Thread(target=job, args=(li, q))
t.start()
threads.append(t)
[thread.join() for thread in threads]
# 从队列中拿出所有线程执行的结果;
results = [q.get() for li in lis]
print(results)
if __name__ == "__main__":
use_thread()
# 结果:
# [10, 44, 199490500, 45496500]
# use_thread函数运行时间:0.00187874
mytimeit.py
import time
def timeit(f):
def wrapper(*args, **kwargs):
start_time = time.time()
res = f(*args, **kwargs)
end_time = time.time()
print("%s函数运行时间:%.8f" % (f.__name__, end_time - start_time))
return res
return wrapper
例2:生产者-消费者模型,用继承实现
什么是生产者-消费者模型?
某个模块专门负责生产+数据, 可以认为是工厂;
另外一个模块负责对生产的数据进行处理的, 可以认为是消费者.
在生产者和消费者之间加个缓冲区(队列queue实现), 可以认为是商店.
生产者 -----》缓冲区 -----》 消费者
优点:
1). 解耦:生产者和消费者的依赖关系减少;
2). 支持并发;是两个独立的个体, 可并发执行;
"""
# 需求1: 给定200个ip地址, 可能开放端口为80, 443, 7001, 7002, 8000, 8080,
9000(flask), 9001
以http://ip:port形式访问页面以判断是否正常访问.
1). 构建所有的url地址;===存储到一个数据结构中
2). 依次判断url址是否可以成功访问
实现多线程:
1). 实例化对象threading.Thread;
2). 自定义类, 继承threading.Thread, 重写run方法(存储任务程序);
"""
def create_data():
"""创建测试数据, 文件中生成200个IP"""
with open('doc/ips.txt', 'w') as f:
for i in range(200):
f.write('172.25.254.%s\n' % (i + 1))
print("测试数据创建完成!")
import time
import threading
from queue import Queue
from urllib.request import urlopen
class Producer(threading.Thread):
def __init__(self, queue):
super(Producer, self).__init__()
self.q = queue
def run(self):
"""生产测试需要的url地址http://ip:port"""
ports = [80, 443, 7001, 7002, 8000, 8080, 9000, 9001]
with open('doc/ips.txt') as f:
for line in f:
ip = line.strip()
for port in ports:
url = "http://%s:%s" %(ip, port)
time.sleep(1)
self.q.put(url)
print("生产者生产url:%s" %(url))
class Consumer(threading.Thread):
def __init__(self, queue):
super(Consumer, self).__init__()
self.q = queue
def run(self):
url = self.q.get()
try:
urlObj = urlopen(url)
except Exception as e:
print("%s不可访问" %(url))
else:
pageContentSize = len(urlObj.read().decode('utf-8'))
print("%s可以访问, 页面大小为%s" %(url, pageContentSize))
def main():
q = Queue()
p = Producer(q)
p.start()
for i in range(400):
c = Consumer(q)
c.start()
if __name__ == '__main__':
# create_data()
main()
- 为什么需要线程锁?
多个线程对同一个数据进行修改时, 可能会出现不可预料的情况.
例如实现银行转账功能,money += 1 这句其实有三个步骤 money; money+1; money=money+1;假如这三步骤还没完成money-=1的线程就开始执行了,后果可想而知,money的值肯定时乱的- 如何实现线程锁?
1. 实例化一个锁对象;
lock = threading.Lock()
2. 操作变量之前进行加锁
lock.acquire()
3. 操作变量之后进行解锁
lock.release()
import threading
# 银行存钱和取钱
def add(lock):
global money # 生命money为全局变量
for i in range(1000000):
# 2. 操作变量之前进行加锁
lock.acquire()
money += 1 # money; money+1; money=money+1;
# 3. 操作变量之后进行解锁
lock.release()
def reduce(lock):
global money
for i in range(1000000):
# 2. 操作变量之前进行加锁
lock.acquire()
money -= 1
# 3. 操作变量之后进行解锁
lock.release()
if __name__ == '__main__':
money = 0
# 1. 实例化一个锁对象;
lock = threading.Lock()
t1 = threading.Thread(target=add, args=(lock,))
t2 = threading.Thread(target=reduce, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
print("当前金额:", money)
例3 多线程实现简单的下载器
"""
当你创建用户界面并想保持界面的可用性时,线程就特别有用。
没有线程,用户界面将变得迟钝,当你下载一个大文件或者执
行一个庞大的数据库查询命令时用户界面会长时间无响应。为
了防止这样情况发生,你可以使用多线程来处理运行时间长的
进程并且在完成后返回界面进行交互。
"""
import threading
from urllib.request import urlopen
DOWNLOAD_DIR = 'doc'
class DownloadThread(threading.Thread):
def __init__(self, url):
super(DownloadThread, self).__init__()
self.url = url
def run(self):
try:
urlObj = urlopen(self.url, timeout=3)
except Exception as e:
print("download %s error\n" % (self.url), e)
imgContent = None
else:
# http://imgsrc.baidu.com/forum/w%3D580/sign=16d420cb8b01a18bf0eb1247ae2e0761/22a4462309f790520522e1d900f3d7ca7bcbd51c.jpg
filename = self.url.split("/")[-1]
# 'wb' === 写的是二进制文件(图片, 视频, 动图, .pdf)
# 'ab'
with open("%s/%s" % (DOWNLOAD_DIR, filename), 'ab') as f:
# 如果文件特别大的时候, 建议分块下载;每次只读取固定大小, 防止占用内存过大.
while True:
imgContentChunk = urlObj.read(1024 * 3)
if not imgContentChunk:
break
f.write(imgContentChunk)
# 可以添加下载的程度(百分率);
print("%s下载成功" % (filename))
# 这些url是我测试用的,是另一个电脑上的一些书的url,速度比较块,可以换成视频的地址进行测试,从网上下载应该会比较慢
url1 = "ftp://172.25.254.250/pub/book/python/01_MIT.Introduction.to.Computation.and.Programming.Using.Python%20revised%20and%20expanded%20edition.pdf"
url2 = 'ftp://172.25.254.250/pub/book/python/02_interview_exercise.pdf'
url3 = "ftp://172.25.254.250/pub/book/python/02_python-data-structure-cn.pdf"
urls = [url1, url2, url3]
for url in urls:
thread = DownloadThread(url)
thread.start()
只能放指定个线程
用法
# python3.2版本之后才有的;
from concurrent.futures import ThreadPoolExecutor
def job(num):
# 需要执行的任务
print("这是一个%s任务" %(num))
return "执行结果:%s" %(num)
if __name__ == '__main__':
# 1. 实例化线城池对象,线城池里面包含5个线程执行任务;
pool = ThreadPoolExecutor(max_workers=5)
futures = []
for i in range(1000):
# 往线程池里面扔需要执行的任务, 返回的是一个对象(_base.Future()),
f1 = pool.submit(job, i)
futures.append(f1)
# 判断第一个任务是否执行结束;
print(futures[0].done(),'------------')
# 获取任务的执行结果;
print(futures[0].result())
上述代码使用map可做以下修改
if __name__ == '__main__':
# 1. 实例化线城池对象,线城池里面包含5个线程执行任务;
pool = ThreadPoolExecutor(max_workers=5)
li=[i for i in range(1000)]
pool.map(job,li)
实现生产者消费者
"""
文件名: {03_生产者_消费者模型_类的继承实现}.py
日期: {2019}-{01}-{19} {10}-{}
作者: lvah
联系: [email protected]
代码描述:
# 需求1: 给定200个ip地址, 可能开放端口为80, 443, 7001, 7002, 8000, 8080,
9000(flask), 9001
以http://ip:port形式访问页面以判断是否正常访问.
1). 构建所有的url地址;===存储到一个数据结构中
2). 依次判断url址是否可以成功访问
实现多线程:
1). 实例化对象threading.Thread;
2). 自定义类, 继承threading.Thread, 重写run方法(存储任务程序);
# 什么是生产者-消费者模型?
某个模块专门负责身缠数据, 可以认为是工厂;
另外一个模块负责对生产的数据进行处理的, 可以认为是消费者.
在生产者和消费者之间加个缓冲区(队列queue实现), 可以认为是商店.
生产者 -----》缓冲区 -----》 消费者
# 优点:
1). 解耦:生产者和消费者的依赖关系减少;
2). 支持并发;是两个独立的个体, 可并发执行;
"""
def create_data():
"""创建测试数据, 文件中生成200个IP"""
with open('doc/ips.txt', 'w') as f:
for i in range(200):
f.write('172.25.254.%s\n' % (i + 1))
print("测试数据创建完成!")
import time
import threading
from queue import Queue
from urllib.request import urlopen
from concurrent.futures import ThreadPoolExecutor
def producer(url):
"""生产测试需要的url地址http://ip:port"""
print("生产者生产url:%s" % (url))
return url
def consumer(future):
print(future.result(),'----------------')
# 获取consumer的返回值;
url = future.result()
try:
urlObj = urlopen(url)
except Exception as e:
print("%s不可访问" % (url))
else:
pageContentSize = len(urlObj.read().decode('utf-8'))
print("%s可以访问, 页面大小为%s" % (url, pageContentSize))
def main():
pool = ThreadPoolExecutor(max_workers=5)
ports = [80, 443, 7001, 7002, 8000, 8080, 9000, 9001]
with open("doc/ips.txt") as f:
for line in f:
ip = line.strip()
for port in ports:
url = "http://%s:%s" % (ip, port)
# producer函数的返回值会回调给consumer函数;
res = pool.submit(producer, url).add_done_callback(consumer)
# create_data()
main()