多任务
当没有多任务时
import time
def sing():
"""唱歌5秒"""
for i in range(5):
print("唱歌中...")
time.sleep(1)
def dance():
"""跳舞5秒"""
for i in range(5):
print("跳舞中")
time.sleep(1)
sing()
dance()
# 唱歌中...
# 唱歌中...
# 唱歌中...
# 唱歌中...
# 唱歌中...
# 跳舞中
# 跳舞中
# 跳舞中
# 跳舞中
# 跳舞中
上面的代码一共运行十秒,先是唱歌,然后再跳舞,这样不符合我们实际的需求,所以我们要做修改。我们将代码使用多任务
import time
import threading
def sing():
"""唱歌5秒"""
for i in range(5):
print("唱歌中...")
time.sleep(1)
def dance():
"""跳舞5秒"""
for i in range(5):
print("跳舞中...")
time.sleep(1)
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
# 唱歌中...跳舞中...
# 唱歌中...跳舞中...
# 跳舞中...唱歌中...
# 跳舞中...唱歌中...
# 跳舞中...
# 唱歌中...
多任务就是操作系统可以同时运行多个任务,也就是,我们在PC端,一边浏览网页一边听歌,一边写报表
- 并行:真的多任务
并发:假的多任务
线程
线程
线程是操作系统能够进行运算调度的最小单位,他被包含在进程中,是进程中的实际运作单位,
一条线程指的是进程中一个单一顺序的控制流,一个进程可以后多个并发的线程,每条线程并行执行不同的任务
线程是一串质量的集合
所有在同一个进程里的线程是共享同一块内存空间
进程和线程的区别
- 线程共享内存空间,进程的内存是独立的
- 同一进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理。
- 创建新线程很简单,创建新进程需要对父进程进行一次克隆
- 一个线程可以控制和同一进程的其他线程,但是进程只能操作子进程
最简单的线程
import threading
def run(n):
print("task", n)
t1 = threading.Thread(target=run,args=("t1",))
t2 = threading.Thread(target=run,args=("t2",))
t1.start() # 启动线程
t2.start()
一个程序运行起来之后,一定有一个执行代码的东西,这个东西我们称之为线程
红色的是主线程,其他颜色是子线程,当start开始的时候子线程开始启动。
而且主线程会等待子线程运行结束。
回忆函数改变局部变量
'''
定义一个全局变量n在调用函数改变,打印两次结果
'''
n = 1
def test(n):
n += 1
print("函数内部 %s" %n)
def test2():
global n
n += 1
'''
全局变量
'''
print(n)
print(id(n))
'''
调用函数
'''
test(n)
print(n)
print(id(n))
'''
调用全局变量的函数
'''
test2()
print(n)
print(id(n))
# 1
# 140732203311504
# 函数内部 2
# 1
# 140732203311504
# 2
# 140732203311536
在一个函数中,对局部变量进行修改的时global的使用方法
- 如果修改了指向,即让全局变量指向了一个新的地方,那么必须使用global
- 如果仅仅只是修改了空间中的数据,不用global
验证线程之间共享
import threading
import time
n = 100
'''
让n产生变化
'''
def test1():
global n
n += 1
print("----in test1 n=%d" % n)
'''
验证这个线程是否共享数据
'''
def test2():
print("---in test2 n = %d" % n)
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1) # 为了让test1先执行完毕
t2.start()
time.sleep(1)
print("---in main Thread n = %d---" % n)
结果
----in test1 n=101
---in test2 n = 101
---in main Thread n = 101---
初识资源竞争
import threading
import time
# 定义一个全局变量
n = 0
def test1(num):
global n
for i in range(num):
n += 1
print('----test1----%d' % n)
def test2(num):
global n
for i in range(num):
n += 1
print('----test2----%d' % n)
t1 = threading.Thread(target=test1, args=(100000,))
t2 = threading.Thread(target=test2, args=(100000,))
t1.start()
t2.start()
# 等待上面的2个线程执行
time.sleep(0.1)
print('---in main Thread n=%d---' %n)
----test1----100000
----test2----197686
---in main Thread n=197686---
互斥锁
当多个线程几乎同时修改某一个共享数据时,可能出现数据错乱,所以我们还要引入互斥锁状态为资源上锁。
当某个线程要修改共享的数据时,先将其锁定称为锁定状态,其他线程无法更改,直达该线程释放资源,其他线程才可以进行修改。
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
import threading
import time
# 定义一个全局变量
n = 0
def test1(num):
global n
# 上锁,如果之前没有上锁,那么上锁成功
# 如果上锁之前已经被上锁了,那么此时会堵塞在这里,直到这个锁被解开
mutex.acquire()
for i in range(num):
n += 1
mutex.release()
print('----test1----%d' % n)
def test2(num):
global n
mutex.acquire()
for i in range(num):
n += 1
mutex.release()
print('----test2----%d' % n)
# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()
t1 = threading.Thread(target=test1, args=(100000,))
t2 = threading.Thread(target=test2, args=(100000,))
t1.start()
t2.start()
# 等待上面的2个线程执行
time.sleep(0.1)
print('---in main Thread n=%d---' %n)
----test1----100000----test2----200000
---in main Thread n=200000---
死锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源而且同时等待对方的资源,就会造成死锁
事件
import threading
event_test = threading.Event()
# event 初试为false
# event.set()方法将其设置为True
# event.clear()方法将其设置为false
# event.wait()方法将其堵塞,一直到TRUE才会解堵塞
# 判断事件为false或者True
print(event_test.is_set()) # 可以验证初试时为false
event_test.set()
print(event_test.is_set()) # 可以验证修改后为true
event_test.clear()
print(event_test.is_set()) # 可以验证修改后为false
import threading
import time
# 全局变量事件
event_test = threading.Event()
def test():
'''
模仿设备运行,5秒钟后出现故障,一秒修复
'''
count = 1
event_test.set()
while True:
if count > 5 and count < 7:
event_test.clear() # 清空标志位
print("警告", count)
elif count >= 7:
event_test.set()
print("解决中",count)
count = 0
else:
print("正常",count)
time.sleep(1)
count += 1
def server(name):
while True:
if event_test.is_set():
print("%s is runing " % name )
time.sleep(1)
else:
print('%s is waitting ' % name)
event_test.wait()
print("%s wait..." % name)
test1 = threading.Thread(target=test,)
test1.start()
server1 = threading.Thread(target=server, args=('Ruijie',))
server1.start()
python多线程,不适合cup密集操作的任务,适合io操作密集型的任务
进程
进程是资源的集合,进程至少有一个线程
最简单的进程
import multiprocessing
import time
def run(name):
time.sleep(2)
print('hello', name)
if __name__ == '__main__':
for i in range(10):
p = multiprocessing.Process(target=run, args=('bob%s' % i,))
p.start()
查看进程ID,确认所有的进程都是由一个父进程创建
from multiprocessing import Process
import os
def info(title):
print(title)
print('module name:', __name__)
print('parent process:', os.getppid()) # 获取父进程id
print('process id:', os.getpid()) # 获取子进程id
print('\n\n')
def f(name):
info('function f')
print('hello', name)
if __name__ == '__main__':
info('main process line')
p = Process(target=f, args=('vv', ))
p.start()
p.join()
进程之间用队列通信
# 不同进程间内存是不共享的,所以我们要通过别的方法实现
# 1. Queues(实现子进程和主进程之间的通信)
from multiprocessing import Process, Queue
def f(q):
q.put([1, 2, 3])
if __name__ == "__main__":
q = Queue() # 主进程
p = Process(target=f, args=(q,)) # 子进程
p.start()
print(q.get())
p.join()
管道传递消息
from multiprocessing import Process, Pipe
def f(conn):
conn.send([1, 2, 3]) # 发送进程传递的消息
conn.close()
if __name__ == "__main__":
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv()) # 接收进程传递的消息
p.join()
manage数据共享
def f(l):
l.append(os.getpid())
print(l)
if __name__ == '__main__':
with Manager() as manager:
l = manager.list(range(5)) # 生成一个可以进行多进程之间的数据共享列表
p_list = []
for i in range(10):
p = Process(target=f, args=(l,))
p.start()
p_list.append(p)
for res in p_list:
res.join()
print(l)
进程锁
from multiprocessing import Process, Lock
def f(l,i):
l.acquire()
print('hello world', i)
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
不加进程锁会出现的问题:
可能会导致屏幕混乱
进程池
from multiprocessing import Process, Pool
import time
import os
def Foo(i):
time.sleep(2)
print("in process", os.getpid())
def Bar(arg):
print("-->exec done", arg, os.getpid())
if __name__ == '__main__':
pool = Pool(processes=3) # 运行进程池同时有5个进程
print("主进程:", os.getpid())
for i in range(10):
pool.apply_async(func=Foo, args=(i,), callback=Bar) # 并行,callback回调,韩式执行完func后执行callback函数
# pool.apply(func=Foo, args=(i,)) # 串行
print("end")
pool.close()
pool.join() # 进程池中进程执行完毕后再关闭
协程
迭代器
迭代器
迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象,
迭代器对象从集合的第一个元素开始访问,直到所有的元素访问完结束,迭代器只能往前不会后台
目前迭代的可以是列表,字典元祖。
# 可迭代对象
for temp in [1, 2, 3]:
print(temp)
for temp in 'abcde':
print(temp)
我们自己定义一个类去迭代自己想打印的东西
class Book(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
book = Book()
book.add('语文')
book.add('数学')
book.add('英语')
# 我们想迭代打印我们添加的课本
for name in book:
print(name)
# TypeError: 'Book' object is not iterable
# 被告知无法迭代
可以迭代的类
class Book(object):
def __init__(self):
self.names = list()
self.current_num = 0
def add(self, name):
self.names.append(name)
# 实现迭代效果必须满足两个条件:
# 1.是否可以迭代
# 2.调用__iter__函数得到对象的__next__方法的返回值
# 3.__iter__方法的返回值是一个迭代器
def __iter__(self):
"""如果想要一个对象称之为可迭代对象,即可以使用for,那么必须实现__iter__方法"""
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
book = Book()
book.add('语文')
book.add('数学')
book.add('英语')
# 我们想迭代打印我们添加的课本
for name in book:
print(name)
# TypeError: 'Book' object is not iterable
# 被告知无法迭代
迭代器储存的是形成数据的方式而不是数据本身
我们发现迭代器最核心的就是可以通过next()函数的调用来返回下一个数据值,如果每次返回的数据值不是一个已有的数据集合中读取的,而是通过按照一定规律计算生成的,那么就意味着可以不用再以来一个已有的数据集合,也就是说不用再将所有的数据一次性缓存下来供后续一次读取,这样可以节约大量空间。
# 普通的裴波那序列
nums = list()
a = 0
b = 1
i = 0
while i < 10:
nums.append(a)
a, b = b, a+b
i += 1
for num in nums:
print(num)
迭代器的斐波拉契
# 迭代器的斐波拉契
import time
class Fibonacci(object):
def __init__(self, all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.current_num < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a + self.b
self.current_num += 1
return ret
else:
raise StopIteration
start = time.time()
fibo = Fibonacci(100)
for num in fibo:
print(num)
end = time.time()
print(end-start)
从这两个案例可以看出用迭代器的更加快
生成器
利用迭代器,我们 可以在每次迭代获取数据 (通过next()方法)时按照特定的规律进行生成。但是我们 在实现一个迭代器时,
关于前迭代到的状态需要我们自己记录,进而才能根据当前状态下生成一个数据。为了达到记录前状态,并配合next()函数进行
迭代使用,我们可以采用更鉴别的语法,即生成器,可以说生成器是一类特殊的迭代器
生成器的第一种方式
将推导式的中括号换成小括号
第二种方式,含有yield的函数
# 函数的fib序列
def fib(nums):
a, b = 0, 1
current_num = 0
while current_num < nums:
print(a)
a, b = b, a+b
current_num += 1
fib(10)
利用yield形成生成器
# 函数的fib序列
def fib(nums):
a, b = 0, 1
current_num = 0
while current_num < nums:
# print(a)
yield a # 如果一个函数中有yield语句,就不是函数,而是一个生成器的模板,暂停功能,函数下次从这里开始运行
a, b = b, a+b
current_num += 1
# 如果在调用fib的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
obj = fib(10)
for num in obj:
print(num)
关于yield的暂停测试
# 函数的fib序列
def fib(nums):
print('-----1----')
a, b = 0, 1
current_num = 0
while current_num < nums:
print('----2-----')
# print(a)
yield a # 如果一个函数中有yield语句,就不是函数,而是一个生成器的模板,暂停功能,函数下次从这里开始运行
print('------3------')
a, b = b, a+b
current_num += 1
print('-----4-----')
# 如果在调用fib的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
obj = fib(10) # obj是生成器对象
ret = next(obj)
print(ret)
ret = next(obj)
print(ret)
ret = next(obj)
print(ret)
代码结果
-----1----
----2-----
0
------3------
-----4-----
----2-----
1
------3------
-----4-----
----2-----
1
从上面测试可以看出,当代码执行到yield时会暂停,当再次执行next()时从这里恢复开始。
yield 关键字有两点作用
- 保存当前运行的状态,然后暂停执行,即将生成器挂起
- 将yield关键字后面表达式的值作为返回值返回
当循环超过设定的值可以用异常抛出
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
obj = create_num(10)
while True:
try:
ret = next(obj)
print(ret)
except Exception as ret:
break
迭代器函数有返回值
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
return 'aaaaaa' # 倘若 函数有一个返回值
obj = create_num(10)
while True:
try:
ret = next(obj)
print(ret)
except Exception as ret:
print(ret.value) # 这里可以提取
break
send
通过send来启动生成器并且可以通过传递参数控制生成器运行。
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
ret = yield a
print(".>>>ret", ret)
if ret != None:
a = ret
a, b = b, a+b
current_num += 1
return 'aaaaaa' # 倘若 函数有一个返回值
obj = create_num(10)
ret = next(obj)
print(ret)
ret = obj.send(55) # 可以传递一个参数,控制生成器的运行结果
print(ret)
while True:
try:
ret = next(obj)
print(ret)
except Exception as ret:
break
结果:
0
.>>>ret 55
1
.>>>ret None
56
.>>>ret None
57
.>>>ret None
113
.>>>ret None
170
.>>>ret None
283
.>>>ret None
453
.>>>ret None
736
.>>>ret None
1189
.>>>ret None
最简单的用yield实现多任务
import time
def task_1():
while True:
print("---1---")
time.sleep(0.5)
yield
def task_2():
while True:
print("---2---")
time.sleep(0.5)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
用gevent实现多任务
import gevent
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5) # 延时切换
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
g1 = gevent.spawn(f1, 5)
g2 = gevent.spawn(f2, 5)
g1.join() # 等待协程结束,当遇到延时时会自动切换
g2.join()
结果:
Greenlet at 0x22959894bf8: f1(5)> 0
<Greenlet at 0x229599f3378: f2(5)> 0
<Greenlet at 0x22959894bf8: f1(5)> 1
<Greenlet at 0x229599f3378: f2(5)> 1
<Greenlet at 0x22959894bf8: f1(5)> 2
<Greenlet at 0x229599f3378: f2(5)> 2
<Greenlet at 0x22959894bf8: f1(5)> 3
<Greenlet at 0x229599f3378: f2(5)> 3
<Greenlet at 0x22959894bf8: f1(5)> 4
<Greenlet at 0x229599f3378: f2(5)> 4