- 多任务的线程
import time
import threading
def sing():
for i in range(5):
print("正在唱:钟无艳")
time.sleep(1)
def dance():
for i in range(5):
print("正在跳popping")
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == "__main__":
main()
以上 就达到了一个目的,唱歌和跳舞同时执行
这就是多任务,现在多核CPU已经非常普及了,但是即使过去的单核cpu也可以多任务,那么单核Cpu是怎么执行的呢, 就是A执行0.01切换到B又执行0.01感觉上是在同时执行,实际上只是单核切换速度快,视觉上达到了这个效果, 真正的并行执行多任务只能在多核CPU上实现,
以下总结以下:
并行: 真的多任务
并发: 假的多任务
线程:
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用
- 使用threading模块
单线程执行
import time
def saySorry():
print("亲爱的,爷错了")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
saySorry()
多线程执行
import threading
import time
def saySorry():
print("亲爱的,爷错了")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=saySorry)
t.start()
** 查看线程熟练 **
import time
import threading
def sing():
for i in range(5):
print("正在唱:钟无艳")
time.sleep(1)
def dance():
for i in range(10):
print("正在跳popping")
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
# enumerate枚举 返回元组比如l =[1,2,3,4]
# for i in enumerate(l)
length = len(threading.enumerate())
print(threading.enumerate())
if length <=1:
break
time.sleep(1)
if __name__ == "__main__":
main()
这里需要引入一个概论,就是主线程不能先于子线程结束。所以默认创建线程的时候,会让主线程等待子线程结束。注意 一般都是在start()的时候,子线程才会被创建。内部的函数执行完 线程才会结束。 并且多个线程之间默认没有先后执行的顺序。
线程执行代码的封装
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更好,使用threading模块时,一般会定义一个新的子类,然后重写run方法。
import threading
import time
class Mythread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = self.name
print(msg)
if __name__ == "__main__":
t = MyThread()
t.start()
说一下 start()方法 会自动调用run方法,run方法写上文提到的Thread(target = 函数名)里面的函数里面的东西,适合于线程的里面调用的方法比较复杂,需要分成多个函数的情况下,比如
import threading
import time
class Mythread(threading.Thread):
def run(self):
self.login()
self.register()
def login(self):
print("登录")
def register(self):
print(注册)
if __name__ == "__main__":
t = MyThread()
t.start()
也就是说你创建了一个线程,这个子线程会顺序执行login和register 注意以上情况只能执行run方法里面调用的方法。别傻了吧唧的在t.start()
后面去调用什么t.login()
多线程共享全局变量
num = 100
nums = [11,22]
def test():
global num
num+=100
def test2():
nums.append(33)
print(num)
print(nums)
test()
test2()
print(num)
print(nums)
在一个函数中对全局变量进行修改的时候,到底是否需要使用goba进行说明
要看是否对全局变量的执行指向进行了修改
如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用 global
如果,仅仅是修改了指向的空间中的数据,此时不用必须使用global
开始看看使用
import threading
import time
#定义一个全局变量
g_num = 100
def test1():
global g.nun
g_num += 1
print("…… in test1 g_num=%d---"%gnun)
def test2():
print("----in telt2 g_num=%d=---"%g_num)
def main():
t1= threading Thread(target=test1)
t2= threading Thread(target=test2)
t1. start()
time. sleep(1)
t2. start()
time. sleep(1)
print("in main Thread g_nuns %s--"%g_num)
if __name__ = "__main__":
main()
这里就是为了说明一个问题,多线程多任务程序,子进程和子进程之间共享全局变量,下面对例子做一个改变
import threading
import time
#定义一个全局变量
def test1(temp):
temp.append(22)
print("…… in test1 temp=%s---"%str(temp))
def test2(temp):
print("----in telt2 temp=%d=---"%str(temp))
g_nums = [11,22]
def main():
# 这个args,是test1的形参
t1= threading Thread(target=test1,args = (g_nums,))
t2= threading Thread(target=test2,args = (g_nums,))
t1. start()
time. sleep(1)
t2. start()
time. sleep(1)
print("in main Thread g_nums = %s--"%str(g_num))
if __name__ = "__main__":
main()
共享全局变量问题
假设两个线程t1和以2都要对全局变量g_num(默认是0)进行加1运算·t1和t2都各对g_num加10次·g_num的
最终的结果应该为20·
但是由于是多线程同时操作·有可能出现下面情况
- 在g_num=0时·t1得g_num=0·此时系统把调度为”sleep状态·把t2转换为running状态t2
也获得g_num=0 - 然后t2对得到的值进行加1并赋给g_num使得g_num=1
- 然后系统又把t2调度为”sleeping"·把t1转为running·线程t1又把它之前得到的0加1后赋值给
g_num. - 这样导致虽然:t1和t2都对g_num加1·但结果仍然是g_num=1
也就是说线程之间发生了资源竞争。
# 定义一个全局变量
g_num = 0
def test1(num):
lobal g_num
for i in rage(num):
g_num += 1
print("---in test1 g_num=%d---"%g_num)
def test2(num):
lobal g_num
for i in rage(num):
g_num += 1
print("---in test2 g_num=%d---"%g_num)
def main():
t1 threading Thread(target=test1, args=(100000,))
t2 threading Thread(target=test2, args=(100000,))
t1. start()
t2. start()
#等待上面的?个线程执行完
time. sleep(5)
print("---in main g_num=%d---"%g_num)
if __name__ == "__main__":
main()
以上的情况,我们预想的是g_num
这个值会加上100000两次,但是实际执行结果并不是这样,也就是说两个线程之间,他们发生了资源争夺,具体争夺过程看上文。
这么去解决这个问题呢,先引入一个同步
的概念
同步就是协同步调·按预定的先后次序进行运行·比如你说完·我再说·
同字从字面上容易理解为一起动作
其实不是·“同字应是指协同·协助·互相配合
如进程·线程同步·可理解为进程或线程A和B一块配合·A执行到一定程度时要依靠B的某个结果·于是
停下来·示息B运行,B执行·再行结果给A,A再继续操作
解决线程同时修改全局变量的方式
上一小节提出的那个计算错误的问题·可以通过线程同步来进行解决
思路·如下
- 系统调用t1·然后获取到g_num的值为0·此时上一把锁,即不允许其他线程操作g_num
- t1对g_num的值进行+1
- t1解锁·此时g_num的值为1·其他的线程就可以使用g_num了·而且g_num的值不是0而是1
4.同理其他线程在对g_num进行修改时·都要先上锁·处理完后再解锁·在上锁的整个过程中不允许其
他线程访问·就保证了数据的正确
互斥锁
当多个线程几乎同时修改某一个共享数据的时候·需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源·最简单的同步机制是引入互斥锁
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时·先行其锁定·此时资源的状态为“锁定“·其他线程不能更改:直到该线程释
放资源,符资源的状态变成“非锁定“·其他的线程才能再次锁定该资源·互斥锁保证了每次只有一个线程
进行写入操作·从而保证了多线程情况下数据的正确性
而threading中定义了Lock类,可以方便的处理锁定:
#创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
注意:
- 如果这个锁之前是没有上锁的,那么acquire不会堵塞
- 如果在调用acquire对这个锁上锁之前,他已经被其他线程上锁,那么此时acquire()会堵塞,直到被解锁为止。
# 定义一个全局变量
g_num = 0
def test1(num):
lobal g_num
# 上锁,如果之前没有被上锁,那么此时 上锁成功
# 如果之前上锁了,此时堵塞,直到锁被解开例如:有三个客户C1,c2,c3,向银行家借款,该银行家的资我总额为10个资金单位,其中C1客户要借
9各资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态
# mutex.acquire() 写在这里,特么和单线程有啥区别,test1执行完了你才解锁,全程test2就在等你test1执行
for i in rage(num):
mutex.acquire()
g_num += 1
# 解锁
mutex.release()
# mutex.release()
print("---in test1 g_num=%d---"%g_num)
def test2(num):
lobal g_num
mutex.acquire()
for i in rage(num):
g_num += 1
mutex.release()
print("---in test2 g_num=%d---"%g_num)
# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()
def main():
t1 threading Thread(target=test1, args=(100000,))
t2 threading Thread(target=test2, args=(100000,))
t1. start()
t2. start()
#等待上面的?个线程执行完
time. sleep(5)
print("---in main g_num=%d---"%g_num)
if __name__ == "__main__":
main()
死锁
class MyThread1(threading.Thread):
def run(self):
# 对mutexA上锁
mutexA.acquire()
# mutexA上锁后,延时1秒,等待另外哪个线程把mutexB上锁
print(self.name+'___do1--up__')
time.sleep(1)
# 此时会阻塞,因为这个mutexB已经被另外的线程抢先上锁了,
mutexB.acquire()
print(self.name+'___do1--down__')
mutexB.release()
# 对mutexA解锁
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
# 对mutexB上锁
mutexB.acquire()
# mutexB上锁后,延时1秒,等待另外哪个线程把mutexB上锁
print(self.name+'___do2--up__')
time.sleep(1)
# 此时会阻塞,因为这个mutexA已经被另外的线程抢先上锁了,
mutexA.acquire()
print(self.name+'___do2--down__')
mutexA.release()
# 对mutexB解锁
mutexB.release()
进程 程序的概念
进程,你就理解成,电脑上的QQ打开,他就是一个进程。 你的QQ里面能够能打开很多窗口,这个窗口就是线程,
创建进程
import threading
import time
import multiprocessing
def test1():
while True():
print("1----")
time.sleep(1)
def test2():
while True:
print("2-----")
time.sleep(1)
def main():
p1 = multiprocessing.Process(target = test1)
p2 = multiprocessing.Process(target = test2)
p1.start()
p2.start()
if __name__ == "__main__":
main()
进程间的通信-Queue
- Queue的使用
可以使用mulitiprocessing模块的Queue实现多进程之间的数据传输,Queue本身是一个消息队列程序,首先用一个小示例来延时一下Queue的工作原理:
from multiprocessing import Queue
q = Queue(3) #初始化一个queue对象,最多可以接收三条put消息
q.put("消息1")
q.put("消息2")
print(q.full()) # False
q.put("消息3")
print(q.full()) #True
# 因为消息队列已经满了,try都会弹出一场,第一个try会等待2秒后再抛出异常,第二个立即抛出
try:
q.put("消息4",True,2)
except:
print("消息队列已满,现有消息数量:%s"%q.qsize())
try:
q.put_nowait("消息4")
except:
print("消息队列已满,现有消息数量:%s"%q.qsize())
# 一下的东西都自己去试试吧
q.put("消息4")
# 这种放入,因为队列是满的 他会阻塞等队列空出来
# 和put的性质差不多,但是这个是取值。
q.get()
q.get_nowait()
应用:
import multiprocessing
def download_from_web(q):
""" 下载数据 """
data = [11,22,33,44]
# 向队列中写入数据
for temp in data:
q.put(temp)
def analysis_data(q):
""" 数据处理 """
waitting_analysis_data = list()
while True:
data = q.get()
waitting_analysis_data.append(data)
if q.empty():
break
def main():
# 1. 创建一个队列
q = multiprocessing.Queue()
# 2. 创建多个进程,将队列的引用当作实参进行传递到里面
p1 = multiprocessing.Process(target = download_from_web,args=(q,))
p2 = multiprocessing.Process(target = download_from_web,args=(q,))
p1.start()
p2.start()
进程池
当需要创建的子进程数量不多时·可以直接利用 multiprocessing中的Process动态成生多个进程·但如果
是上百甚至上千个目标·手动的去创建进程的工作量巨大·此时就可以用
multiprocessing块提供的Pool方法
初始化Pool时·可以指定一个最大进程数·当有新的请求交到Pool中时·如果池还没有满那么就会创
建一个新的进程用来执行该请求:但如果池中的进程数已经达到指定的最大值·那么该请求就会等待·直
到池中有进程结束·才会用之前的进程来执行新的任务·请看下面的实例
from multiprocessing import Pool
import os,time,random
def worker(msg):
t_start = time.time()
print("开始执行,进程为%d"%(msg,os.getpid()))
# random.random() 随机生成0~1之间的浮点数
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"执行完毕,耗时%0.2f"%(t_stop-t_start))
po = Pool(3) # 定义一个进程池,醉倒进程数为3
for i in range(0,10):
# Pool().apply_async(要调用的目标,(传递给目标参数元组,))
po.apply_async(worker,(i,))
print("-----start-----")
po.close()
# 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完毕,必须放在close语句之后,没这句话主进程就直接死了。这个算一个插队吧,把进程池创建的进程插队到主进程这里来,让主进程等子进程执行完了再死。
print("---end----")
协程
迭代器
- 可迭代对象
我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
但是,是否所有的数据类型都可以放到for…in…的语句中,然后让for…in…每次从中取出一条数据供我们使用,鸡供我们迭代吗?
from collections import Iterable
from collections import Iterator
# 看[1,2,3]是不是iterable类型的
isinstance([1,2,3],Iterable)
- 自己创建一个可迭代对象
from collections import Iterable
from collections import Iterator
class Classmate(object):
def __init__(self):
self.names = list()
def add(self,name):
self.names.append(name)
def __iter__(self):
""" 如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法
但是如果只有这个,我们是不是差一个可以记录你每一次取值的东西,所以需要__iter__返回一个具有__iter__方法和__next
__方法的引用。
"""
return ClassIterator(self)
class ClassIterator(object):
def __init__(self,obj):
self.obj = obj
self.currentnum = 0
def __iter(self):
pass
def __next__(self):
if self.current_num<len(self.obj.names):
ret = self.obj.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration
classmate=Classmate()
classmate.add("老王")
classmate.add("王二")
classmate.add("张三")
print(isinstance(classmate,Iterable))
classmate_iterator = iter(classmate)
print(isinstance(classmate_iterator,Iterator))
print(next(classmate_iterator))
两个类方便理解,下面整合成一个类
class Classmate(object):
def __init__(self):
self.names = list()
self.current_num = 0
def add(self,name):
self.names.append(name)
def __iter__(self):
return self
def __next__(self):
if self.current_num<len(self.names):
ret = self.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration
使用for循环的条件
- 判断xxx_obj是否是可以迭代的
- 再第一步成立的前提下,调用iter函数得到xxx_obj对象的__iter__方法的返回值。
- __iter__方法的返回值是一个迭代器。
这里说一下,并不是只有foi循环能接收可迭代对象,强转类型基本都用到了可迭代对象
生成器
利用迭代器·我们可以在每次迭代获取数据(通过next方法)时按照特定的规律进行生成·但是我们在实
现一个迭代器时·关于当前迭代到的状态需要我们自己记录·进而才能根据当前状态生成下一个数据·为
了达到记录当前状态·并配合next()函进行选代使用·我们可以采用更简便的语法·即生成器
generator)·生成器是一类特殊的迭代器
这里有点意思,我第一学到列表生成器之后,紧接着学到了元组,我寻思是不是也有个和列表生成器一样的东西来生成元组
l = [x for x in range(10)]
==>>
l = (x for x in range(10))
结果发现,特么打印出来的是个什么鬼东西,我完全不认识啊,后面慢慢学习的时候才发现,这个玩意就是生成器
当然上面的方法,在真正运用到生成器的时候 运用的比较少,下面才是真正的用法
def fib(n):
current = 0
num1,num2 = 0,1
while current < n:
num = num1
num1, num2 = num2,num1+num2
current += 1
yield num
return "done"
注意到有个yield
函数里面有yield语句就是个生成器的模板。如果调用fib时,它有yield,不是调用函数,而是创建一个生成器对象。
一定要注意 yield 它是个特殊的迭代器,可以直接通过next()取值。
通过send调用传参
def fib(n):
a,b = 0,1
current_num = 0
while current_num < all_num:
ret = yield a
print(ret)
a,b = b,a+b
current_num += 1
obj = create_num(10)
ret = obj.send("hah")
print(ret)
send会在yeild暂停之后,第二次启动的时候执行上面的赋值语句,把’hah’赋值给ret
使用yield完成多任务
improt time
def task_1():
while True:
print("---1---")
time.sleep(0.1)
yield
def task_2():
while True:
print("---2---")
time.sleep(0.1)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
安装 greenlet
pip3 install greenlet
import greenlet import greenlet
import time
def task_1():
while True:
print("---1---")
gr2.switch()
time.sleep(0.5)
def task_2():
while True:
print("---2---")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
以上基本实现了协程,但是这个得自己代码切换,有点麻烦gevent
直接来统一给你管理。
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
# 使用gevent的阻塞 比如socket里面的阻塞都给爷换成这个,是不是很蠢嘻嘻,所以下面讲到打补丁
gevent.sleep(0.5)
# spawn 下了个卵(卵的执行函数,参数)不是我恶趣味叫他卵哈,特么这玩意真的翻译是个卵
g1 = gevent.spawn(f,5)
g2 = gevent.spawn(f,5)
g3 = gevent.spawn(f,5)
g1.join()
g2.join()
g3.join()
打补丁
import gevent
#打上这个补丁之后,就普通阻塞操作变成了gevent的阻塞
monkey.path_all()
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
time.sleep(0.5)
#g1 = gevent.spawn(f,5)
#g2 = gevent.spawn(f,5)
#g3 = gevent.spawn(f,5)
#g1.join()
#g2.join()
#g3.join()
gevent.joinall([
gevent.spawn(f,5),
gevent.spawn(f,5)
])
简单总结
1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源很最大·效率很低
4.线程切换需要的资源一般·效率一般(当然了在不考虑GIL的情况下)
5.协程切换任务资源很小·效率高
6.多进程·多线程根据cpu核数不一样可能是并行的·但是协程是在一个线程中所以是并发