Python中的线程
文章目录
一.创建线程
线程:CPU的基本调度单位,是程序执行流的最小单元,线程有就绪,阻塞和运行三种状态,就绪就是指线程具备所有的运行条件,逻辑上可以运行,只需等待CPU分配时间片即可,运行状态就是指线程占有CPU时间片,正在运行,阻塞是指线程在等待一个事件(如某个信号量),逻辑上不可执行,每一个程序至少有一个线程。线程是程序中一个单一的顺讯控制流程,进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的调度单位,在单个程序中同时运行多个线程完成不同的工作,称为多线程。
import _thread #较为底层的线程创建方式
import threading #较常用
import time
def job():
print("这是一个需要执行的任务")
# 激活的线程个数
print("当前线程的个数:", threading.active_count())
# 打印当前线程的详细信息
print("当前线程信息:", threading.current_thread())
time.sleep(100)
if __name__ == "__main__":
job()
二.创建多线程
1._thread方式创建多线程
import _thread
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 threading
import time
def job(name):
print("这是一个需要执行的任务: %s" %(name))
# 激活的线程个数
print("当前线程的个数:", threading.active_count())
# 打印当前线程的详细信息
print("当前线程信息:", threading.current_thread())
time.sleep(100)
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方法
join
方法是使程序等待所有的子线程执行结束之后, 继续执行主线程的内容,即等待,直到t1线程执行结束;其间阻塞正在调用的线程。
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=("Light Dance",))
t2 = threading.Thread(target=code, args=("多线程", ))
t1.start()
t2.start()
# 等待所有的子线程执行结束之后, 继续执行主线程的内容;
t1.join()
t2.join()
print("花费时间: %s" %(time.time()-start_time))
四.threading的set_daemon方法
# 当主线程执行结束, 让没有执行的线程强制结束;set_daemon
import threading
import time
# 任务1:
def music(name):
for i in range(2):
print("正在听音乐%s" %(name))
time.sleep(1)
# 任务2:
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=("Light Dance",))
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.任务无需任何参数
import threading
# 类的继承
class IpThread(threading.Thread):
# 重写构造方法;
def __init__(self, jobname):
super(IpThread, self).__init__()
self.jobname = jobname
# 将多线程需要执行的任务重写到run方法中;
def run(self):
print("this is a job")
t1 = IpThread(jobname="new job")
t1.start()
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(lock):
# 2. 操作变量之前进行加锁
global money
for i in range(1000000):
money += 1
# 3. 操作变量完成后进行解锁
def reduce(lock):
# 2. 操作变量之前进行加锁
global money
for i in range(1000000):
money -= 1
# 3. 操作变量完成后进行解锁
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))
加锁之后:
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()
八.线程池
from concurrent.futures import ThreadPoolExecutor #导入线程池,注意: python3.2版本以后才可以使用
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/'] * 100
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]
# 注意: 传递的时包含futures对象的序列, as_complete, 返回已经执行完任务的future对象,
# 直到所有的future对应的任务执行完成, 循环结束;
# for finish_fs in as_completed(futuresObj):
# print(finish_fs.result() )
for future in futuresObj:
print(future.result())
print("submit执行时间:%s" %(time.time()-start_time))
#
# 方法2:通过map方式执行
start_time = time.time()
pool = ThreadPoolExecutor(max_workers=20)
for res in pool.map(get_page, URLS):
print(res)
print("map执行时间:%s" %(time.time()-start_time))
综上,map处理的稍快一些,而且网速不是很稳定,但考虑到map更加简洁,推荐使用map处理。