一 创建线程
import threading
import time
def job():
print("这是一个需要执行的任务")
# 激活的线程个数
print("当前线程的个数:", threading.active_count())
# 打印当前线程的详细信息
print("当前线程信息:", threading.current_thread())
time.sleep(10)
if __name__ == "__main__":
job()
二 创建多线程
1._thread方式创建多线程
import _thread
import threading
import time
def job(name):
print("这是一个需要执行的任务")
# # 激活的线程个数
# print("当前线程的个数:", threading.active_count())
# # 打印当前线程的详细信息
# print("当前线程信息:", threading.current_thread())
print(name, time.ctime())
time.sleep(2)
if __name__ == "__main__":
# 创建多个线程, 但是没有开始执行任务;
_thread.start_new_thread(job,('thread1', ))
_thread.start_new_thread(job,('thread2', ))
while True:
pass
2.threading方式创建多线程
import _thread
import threading
import time
def job(name):
print('这是一个需要执行的任务: %s' %(name))
#激活的线程个数
print('当前线程的个数:',threading.active_count())
#打印当前线程的详细信息
print('当前线程信息:',threading.current_thread())
time.sleep(3)
print(name,time.ctime())
if __name__=='__main__':
job('job0')
#创建多个线程
t1=threading.Thread(target=job,name='job1',args=('job1-name',))
t1.start()
t2=threading.Thread(target=job,name='job2',args=('job2-name',))
t2.start()
print('hello')
三 多线程的JOIN方法
不使用多线程
import time
def music(name):
for i in range(2):
print('正在听音乐%s' %(name))
time.sleep(1)
def code(name):
for i in range(2):
print('正在编写代码%s' %(name))
time.sleep(2)
if __name__=='__main__':
start_time=time.time()
music('That girl')
code('爬虫')
print('花费时间:%s' %(time.time()-start_time))
结论:事情是一个一个的做,没有同时进行,但是我敲代码的时候是会听歌的,因此此处可以使用多线程来处理的
使用多线程
import threading
import time
def music(name):
for i in range(2):
print('正在听音乐%s' %(name))
time.sleep(1)
def code(name):
for i in range(2):
print('正在编写代码%s' %(name))
time.sleep(2)
if __name__=='__main__':
start_time=time.time()
t1=threading.Thread(target=music,args=('That girl',))
t2=threading.Thread(target=code,args=('爬虫',))
t1.start()
t2.start()
print('花费时间:%s' %(time.time()-start_time))
结论:发现时间确实是变快了好多,但是感觉跟想象的不太一样,执行时间都结束了,而事情却没做完,这是因为主线程阻塞了子线程进行,接着我们需要引入join方法
import threading
import time
def music(name):
for i in range(2):
print('正在听音乐%s' %(name))
time.sleep(1)
def code(name):
for i in range(2):
print('正在编写代码%s' %(name))
time.sleep(2)
if __name__=='__main__':
start_time=time.time()
t1=threading.Thread(target=music,args=('That girl',))
t2=threading.Thread(target=code,args=('爬虫',))
t1.start()
t2.start()
#等待所有的子线程执行结束之后,继续执行主线程的内容
t1.join()
t2.join()
print('花费时间:%s' %(time.time()-start_time))
join方法是使程序等待所有的子线程执行结束之后, 继续执行主线程的内容,即等待,直到t1线程执行结束;其间阻塞正在调用的线程。
四 threading的set_daemon方法
当主线程执行结束, 让没有执行的线程强制结束;set_daemon
import threading
import time
#任务一
def music(name):
for i in range(2):
print('正在听音乐%s' %(name))
time.sleep(1)
#任务二
def code(name):
for i in range(2):
print('正在编写代码%s' %(name))
time.sleep(2)
if __name__=='__main__':
start_time=time.time()
t1=threading.Thread(target=music,args=('That girl',))
t2=threading.Thread(target=code,args=('爬虫',))
#将t1线程为守护线程,如果设置True,子线程启动,当主线程执行结束,子线程也结束
#设置setDaemon必须在启动线程之前进行设置
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
print('花费时间:%s' %(time.time()-start_time))
五 继承的方法创建多线程
1.任务无需任何参数
2.任务需要参数
import json
import threading
#类的继承
from urllib.error import HTTPError
from urllib.request import urlopen
import time
class IpThread(threading.Thread):
# 重写构造方法;如果执行的任务需要传递参数,那将参数通过构造函数与self绑定;
def __init__(self, jobname, ip):
super(IpThread, self).__init__()
self.jobname = jobname
self.ip = ip
# 将多线程需要执行的任务重写到run方法中;
def run(self):
try:
# 需要有一个参数,传ip;
url = "http://ip.taobao.com/service/getIpInfo.php?ip=%s" % (self.ip)
# 根据url获取网页的内容, 并且解码为utf-8格式, 识别中文;
text = urlopen(url).read().decode('utf-8')
except HTTPError as e:
print("Error: %s获取地理位置网络错误" %(self.ip))
else:
# 将获取的字符串类型转换为字典,方便处理
d = json.loads(text)['data']
country = d['country']
city = d['city']
print("%s:" % (self.ip), country, city)
def use_thread():
start_time = time.time()
ips = ['172.25.254.250', '8.8.8.8',
'172.25.254.250', '8.8.8.8',
'172.25.254.250', '8.8.8.8']
threads = []
for ip in ips:
t = IpThread(jobname="爬虫", ip=ip)
threads.append(t)
t.start()
# 等待所有的子线程执行结束
[thread.join() for thread in threads]
print("Success, 运行时间为%s" % (time.time() - start_time))
if __name__ == "__main__":
use_thread()
六 线程锁
多个线程对同一个数据进行修改时,最大的危险在于把内容给改乱,如下:
import threading
def add():
global money
for i in range(1000000):
money+=1
def reduce():
global money
for i in range(1000000):
money-=1
if __name__=='__main__':
money=0
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=reduce)
t1.start()
t2.start()
#等待所有子线程执行结束
t1.join()
t2.join()
print("最终金额为:%s" % (money))
分析:理论上money的值应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,money的结果就不一定是0了
原因:高级语言的一条语句在CPU执行时是若干条语句,即使一个简单计算money+=1 也是分为两步去完成的,先计算 money+1,将结果存入临时变量,再将临时变量赋值给money,而两个线程都拥有各自的临时变量,当t1与t2交替执行的时候,就会导致把同一个对象给改的混乱
所以我们对其进行加锁:线程锁lock
import threading
def add(lock):
# 2.操作变量之前进行加锁
lock.acquire()
global money
for i in range(1000000):
money += 1
# 3.操作变量完成后进行解锁
lock.release()
def reduce(lock):
# 2.操作变量之前进行加锁
lock.acquire()
global money
for i in range(1000000):
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("最终金额为:%s" %(money))
线程锁的本质:对同一对象进行操作时,一个线程在进行前加把锁,让其他线程不能对其进行操作,执行完成后,再进行解锁释放
七 Python中的GIL全局解释器锁
- GIL(全局解释器锁)
- python解释器默认每次只允许一个线程执行
执行过程:
1). 设置GIL
2). 切换到线程去运行对应的任务;
3). 运行
-执行完了
-time.sleep()
-获取其他信息才能继续执行, eg: 从网络上获取网页信息等;
4). 把线程设置为睡眠状态
5). 解锁GIL
6). 再次重复执行上述内容
方法的选择:
Python并不支持真正意义上的多线程。Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意。
Python中有一个被称为Global Interpreter Lock(GIL)的东西,它会确保任何时候你的多个线程中,只有一个被执行。线程的执行速度非常之快,会让你误以为线程是并行执行的,但是实际上都是轮流执行。
经过GIL这一道关卡处理,会增加执行的开销。这意味着,如果你想提高代码的运行速度,使用threading包并不是一个很好的方法。
import threading
from mytimeit import timeit
def job(l):
sum(l)
@timeit
def use_thread():
li = range(1,10000)
for i in range(5):
t = threading.Thread(target=job, args=(li, ))
t.start()
@timeit
def use_no_thread():
li = range(1, 10000)
for i in range(5):
job(li)
if __name__ == "__main__":
use_thread()
use_no_thread()
mytime模块中的timeit装饰器:
import time
def timeit(f):
def wrapper(*args, **kwargs):
start_time = time.time()
res = f(*args, **kwargs)
end_time = time.time()
print("%s运行时间为%s" %(f.__name__, end_time-start_time))
return res
return wrapper
八 队列与多线程
1).理论上多线程执行任务, 会产生一些数据, 为其他程序执行作铺垫;
2). 多线程是不能返回任务执行结果的, 因此需要一个容器来存储多线程产生的数据
3). 这个容器如何选择? list(栈, 队列), tuple(x), set(x), dict(x), 此处选择队列来实现
import threading
from collections import Iterable
from mytimeit import timeit
from queue import Queue
def job(l, queue):
# 将任务的结果存储到队列中;
queue.put(sum(l))
@timeit
def use_thread():
# 实例化一个队列, 用来存储每个线程执行的结果;
q = Queue()
# 入队
# q.put(1)
li = [[1,2,3,4,5], [2,3,4,5,6], [2,3,4,5,6,7,8], [2,3,4,5,6]]
threads = []
for i in li:
t = threading.Thread(target=job, args=(i, q))
threads.append(t)
t.start()
# join方法等待所有子线程执行结束
[thread.join() for thread in threads]
# 从队列里面拿出所有的运行结果
result = [q.get() for _ in li]
print(result)
print(isinstance(q, Iterable))
if __name__ == "__main__":
use_thread()
九 线程池
from concurrent.futures import ThreadPoolExecutor
import time
#需要执行的任务
def job():
print('This is a job')
return 'hello'
if __name__=='__main__':
#实例化对象,线程池包含10个线程来处理任务
pool=ThreadPoolExecutor(max_workers=10)
#往线程池里面扔需要执行的任务, 返回一个对象,( _base.Future实例化出来的)
f1=pool.submit(job)
f2=pool.submit(job)
#判断任务是否执行结束
print(f1.done())
time.sleep(1)
print(f2.done())
#获取任务执行的结果
print(f1.result())
print(f2.result())
十 线程池中的submit提交与map方法
from urllib.error import HTTPError
from urllib.request import urlopen
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
import time
URLS = ['http://httpbin.org', 'http://example.com/',
'https://api.github.com/'] * 50
def get_page(url, timeout=3):
try:
content = urlopen(url).read()
return {'url':url, 'len':len(content)}
except HTTPError as e:
return {'url':url, 'len':0}
# 方法1: submit提交任务
start_time = time.time()
pool = ThreadPoolExecutor(max_workers=20)
futuresObj = [pool.submit(get_page, url) for url in URLS]
for future in futuresObj:
print(future.result())
print("执行时间:%s" %(time.time()-start_time))
综上map处理的稍快一些,考虑到map更加简洁,推荐使用map处理。