文章目录
线程-局部变量
多线程之间使用threading.local
对象用来存储数据,而其他线程不可见.
实现多线程之间的数据隔离.
本质上就是不同的线程使用这个对象时,为其创建一个只属于当前线程的字典.
拿空间换时间的方法.
from threading import local,Thread
loc = local()
print(loc)
def func(name,age):
global loc
loc.name = name
loc.age = age
print(loc.name,loc.age)
#创建第一个线程
t1 = Thread(target=func,args=("黄熊大",98))
t1.start()
#创建第二个线程
t2 = Thread(target=func,args=("黄将用",18))
t2.start()
Event 事件
wait()
动态添加阻塞
set()
将内部属性改成True
clear()
将内部属性改成False
is_set()
判断当前属性(默认为False)
语法:
e = Event()
print(e.is_set())
# wait(timeout = 3) 最多阻塞等待3秒.
e.wait(3)
print(e.is_set())
模拟链接数据库
from threading import Event,Thread
import time,random
def check(e):
print("开始检测数据连接的合法性")
time.sleep(random.randrange(1,7))
e.set()
def connect(e):
sign = False
# 最多尝试连接3次数据库,连接不上就报错,报超时等待错误,TimeoutError
for i in range(3):
e.wait(1)
if e.is_set():
sign = True
print("数据库连接成功")
break
else:
print("尝试连接数据库%s次失败了" % (i+1))
if sign == False:
# 主动抛出异常
raise TimeoutError
e = Event()
# 创建线程1
Thread(target=check,args=(e,)).start()
# 创建线程2
Thread(target=connect,args=(e,)).start()
Condition 条件
wait 和 notify 他俩是一对:
wait
负责添加阻塞
notify
负责释放阻塞
语法 无法是wait 还是notify , 在使用时,前后必须上锁
- 语法:wait 前后上锁
acquire()
wait()
...code...
release()
- 语法:notify 前后上锁
acquire()
notify(自定义释放多少阻塞,释放多少线程数量,默认放行1个)
release()
from threading import Condition,Thread #(了解)
import time
def func(con,index):
print("%s在等待" % (index))
con.acquire()
# 添加阻塞
con.wait()
print("%s do something" % (index))
con.release()
con = Condition()
lst = []
for i in range(10):
t = Thread(target=func,args=(con,i))
t.start()
lst.append(t)
# 不能通过循环列表,让线程.join的方法实现,因为每个线程任务都上锁了,必须解锁之后才能向下执行.
# for i in lst:
# i.join()
time.sleep(1)
# 写法一
con.acquire()
# 一次性释放所有阻塞,所有线程
# con.notifyAll()
con.notify(5)
con.release()
# 写法二
count = 10
while count>0:
num = int(input(">>>\n"))
con.acquire()
con.notify(num)
con.release()
count-=num
Timer 定时器(了解)
Timer 几秒之后执行某个任务
Timer(时间,执行的任务)
from threading import Timer
def func():
print("正在执行某个任务...")
t = Timer(3,func)
t.start()
print("主线程...")
# 在实际生产中,利用linux的计划任务来取代 , crontab 来取
queue 队列
put
往队列里放值,超过队列长度,直接阻塞
get
获取值,如果获取不到,阻塞
put_nowait()
如果放入的值超过了队列长度,直接报错
get_nowait()
如果获取的值已经没有了,直接报错
- queue 先进先出
from queue import Queue
q = queue.Queue()
q.put(1)
q.put(2)
print(q.get())
# print(q.get())
# print(q.get())
# 线程中支持get_nowait
print(q.get_nowait())
# 可以限制队列的长度
q = queue.Queue(2)
q.put(3)
q.put(4)
# q.put(5) # 加了阻塞
q.put_nowait(6)
- LifoQueue 后进先出 (数据结构中,栈队列的存取顺序)
from queue import LifoQueue
lq = LifoQueue()
lq.put(1)
lq.put(2)
print(lq.get())
- PriorityQueue 按照优先级顺序排序
默认按照数字大小排序,然后在按照ascii编码从小到大排序
from queue import PriorityQueue
pq = PriorityQueue()
pq.put( (12,"zhangsan") )
pq.put( (6, "lisi" ) )
pq.put( (6,"lijiujiu") )
pq.put( (18,"wangwu" ) )
print(pq.get())
print(pq.get())
print(pq.get())
print(pq.get())
单独一个元素,必须放同一种类型,
- 如果是数字 [默认从小到大排序]
pq = PriorityQueue()
pq.put(13)
pq.put(18)
pq.put(3)
# pq.put("abc")
print(pq.get())
print(pq.get())
print(pq.get())
# print(pq.get())
- 如果是字符串 [默认按照ascii编码从小到大排序]
pq = PriorityQueue()
pq.put("aabb")
pq.put("ddbb")
pq.put("ccbb")
pq.put("王五")
pq.put("李四")
# pq.put(990) #error
print(pq.get())
print(pq.get())
print(pq.get())
更新版进程池和线程池
ProcessPoolExecutor 进程池的基本使用(改良版)
相对于旧版的进程池,
一定会等待子进程全部执行完毕之后,再终止程序,相当于过去的Process流程
shutdown
相当于 Process里面的join
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time
def func(i):
print("Process",i,os.getpid())
time.sleep(0.1)
print("Process...end")
return 88899
if __name__ == "__main__":
# (1) ProcessPoolExecutor() <==> Pool()
p = ProcessPoolExecutor(5)
# (2) submit() <==> apply_async()
res = p.submit(func,55)
# (3) result() <==> get()
res = res.result()
print(res) # 88899
# (4) shutdown <==> close + join
# p.shutdown()
print("主进程执行结束...")
ThreadPoolExecutor 线程池
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread as cthread
def func(i):
print("thread",i,cthread().ident)
time.sleep(0.1)
print("thread %s end" % (i))
# 可以在参数中指定并发的线程数
tp = ThreadPoolExecutor(10)
for i in range(20):
tp.submit(func,i)
tp.shutdown()
print("主线程执行结束...")
线程池的返回值
from threading import current_thread as cthread
def func(i):
print("thread",i,cthread().ident)
# 加延迟防止个别线程因为执行速度过快,又接受了新任务,阻碍新线程的创建.
# time.sleep(0.1)
print("thread %s end" % (i))
# return "*"* i
return cthread().ident
tp = ThreadPoolExecutor(5)
lst = []
setvar = set()
for i in range(10):
res = tp.submit(func,i)
lst.append(res)
for i in lst:
# print(i.result())
setvar.add(i.result())
print(setvar,len(setvar))
print("主线程执行结束...")
map 返回迭代器
def func(i):
print("thread",i,cthread().ident)
time.sleep(0.1)
print("thread %s end" % (i))
return "*"* i
tp = ThreadPoolExecutor(5)
it = tp.map(func,range(20)) #map
from collections import Iterable,Iterator
print(isinstance(it,Iterator))
for i in it:
print(i)
tp.shutdown()
print("主线程执行结束")
回调函数
把函数当成参数传递给另外一个函数
函数先执行,最后在执行当参数传递的这个函数,整个过程是回调,这个参数是回调函数.
- 线程池的回调函数是由 子线程完成
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread as cthread
import time
def func(i):
print("thread",i,cthread().ident)
time.sleep(0.1)
print("thread %s end" % (i))
return "*" * i
# 定义成回调函数
def call_back(args):
print("call back: ",cthread().ident)
print(args.result())
tp = ThreadPoolExecutor(5)
for i in range(1,11):
# submit(函数,参数).add_done_callback(要添加的回调函数)
tp.submit(func,i).add_done_callback(call_back)
tp.shutdown()
print("主线程:",cthread().ident)
- 进程池的回调函数是由 主进程完成
from concurrent.futures import ProcessPoolExecutor
import os,time
def func(i):
print("Process",i,os.getpid())
time.sleep(0.1)
print("Process %s end" % (i))
if __name__ == "__main__":
p = ProcessPoolExecutor(5)
p.submit(func,11)
p.shutdown()
print("主进程:",os.getpid())
from concurrent.futures import ProcessPoolExecutor
import os,time
def func(i):
print("Process",i,os.getpid())
time.sleep(0.1)
print("Process %s end" % (i))
return i * "*"
# 回调函数
def call_back(args):
print("call back :",os.getpid())
# print(args)
print(args.result())
if __name__ == "__main__":
# 同一时间最多允许5个进程并发
tp = ProcessPoolExecutor(5)
for i in range(1,11):
tp.submit(func,i).add_done_callback(call_back)
tp.shutdown()
print("主进程id:",os.getpid())
协程
协程也叫纤程: 协程是线程的一种实现.
指的是一条线程能够在多任务之间来回切换的一种实现.
对于CPU、操作系统来说,协程并不存在.
任务之间的切换会花费时间.
目前电脑配置一般线程开到200会阻塞卡顿.
- 用协程改写生产者消费者模型
def producer():
for i in range(100):
yield i
def consumer():
g = producer()
for i in g:
print(i)
consumer()
协程的具体实现
协程帮助你记住哪个任务执行到哪个位置上了,并且实现安全的切换
一个任务一旦阻塞卡顿,立刻切换到另一个任务继续执行,保证线程总是忙碌的,更加充分的利用CPU,抢占更多的时间片
一个线程可以由多个协程来实现,协程之间不会产生数据安全问题
-
协程模块
-
greenlet
gevent的底层,协程,切换的模块
gevent
直接用的,gevent能提供更全面的功能
switch
利用它进行任务的切块,一般在阻塞的时候切换
只能默认手动切换
- 缺陷:不能够规避io,不能自动实现遇到阻塞就切换
from greenlet import greenlet
import time
def eat():
print("eat one1")
# 手动切换到play这个协程中
g2.switch()
time.sleep(1)
print("eat one2")
def play():
print("play one1")
time.sleep(1)
print("play one2")
g1.switch()
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()
- 缺陷 :
gevent
不能够识别time.sleep
是阻塞
import gevent
# gevent 其中有一个spawn 类似于switch,也是切换任务的
def eat():
print("eat one1")
time.sleep(1)
print("eat one2")
def play():
print("play one1")
time.sleep(1)
print("play one2")
# 利用gevent 创建协程对象g1
g1 = gevent.spawn(eat)
# 利用gevent 创建协程对象g2
g2 = gevent.spawn(play)
# 协程的阻塞是join 等待当前协程执行完毕之后,在向下执行
g1.join() # 阻塞直到g1协程执行完毕
g2.join() # 阻塞直到g2协程执行完毕
print("主线程执行完毕")
- 进阶 用gevent.sleep 来取代time.sleep()
import gevent
def eat():
print("eat one1")
gevent.sleep(1)
print("eat one2")
def play():
print("play one1")
gevent.sleep(1)
print("play one2")
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
print("主线程执行完毕")
- 终极解决识别问题
spawn
gevent中的spawn遇到阻塞会自动切换协程任务
from gevent import monkey
# 把patch_all下面引入的所有模块中的阻塞识别出来
monkey.patch_all()
import time
import gevent
def eat():
print("eat one1")
time.sleep(1)
print("eat one2")
def play():
print("play one1")
time.sleep(1)
print("play one2")
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
g1.join()
g2.join()
print("主线程执行结束..")
协程相关函数
spawn(函数,参数1,参数2.....)
启动一个协程join()
阻塞,直到某个协程执行完毕joinall()
等待所有协程执行完毕任务.g1.join()
g2.join()
gevent.joinall([g1,g2])
参数是一个列表,1和2并在一起 等于 3
value
获取协程返回值
joinall value 这两个函数用法
from gevent import monkey;monkey.patch_all()
# 两句话可以合并在一句话之后,但是语句和语句之间用分号隔开
import gevent
import time
def eat():
print("eat one1")
time.sleep(1)
print("eat one2")
return "吃完了"
def play():
print("play one1")
time.sleep(1)
print("play one2")
return "玩完了"
g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
# 等待g1,g2两个协程任务全都执行完毕之后,在向下执行
gevent.joinall([g1,g2])
# 获取协程返回值
print(g1.value)
print(g2.value)
利用协程爬取页面数据
HTTP 状态码:
200 ok
400 Bad Request
404 Not Found
- 爬虫基础
import requests
# 抓取网站信息,返回对象
response = requests.get("http://www.4399.com/")
print(response)
# 获取状态码
res = response.status_code
print(res)
# 获取字符编码集
res_code = response.apparent_encoding
print(res_code)
# 设置编码集
# response.encoding = res_code
# 获取网页里面的内容
res = response.text
print(res)
- 爬虫例子
import requests,time,gevent
url_list = [
"http://www.baidu.com",
"http://www.taobao.com",
"http://www.4399.com",
"http://www.jd.com",
"http://www.7k7k.com"
]
def get_url(url):
response = requests.get(url)
if response.status_code == 200:
print(response.text)
- 正常爬数据
start = time.time()
for i in url_list:
get_url(i)
end = time.time()
print(end-start) #22.8345787525177
- 用协程爬取数据
lst = []
start = time.time()
for i in url_list:
g = gevent.spawn(get_url,i)
lst.append(g)
gevent.joinall(lst)
end = time.time()
print(end - start) #1.053304672241211