Python笔记_32_线程_协程

线程-局部变量

多线程之间使用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()等待所有协程执行完毕任务.
    1. g1.join()
    2. g2.join()
    3. 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)
  1. 正常爬数据
start = time.time()
for i in url_list:
	get_url(i)
end = time.time()
print(end-start)  #22.8345787525177
  1. 用协程爬取数据
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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值