文章目录
多任务编程
什么是多任务编程?
就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运⾏着,只是桌⾯上没有显示⽽已。
单核CPU如何实现“多任务”呢?
操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。我们把这种情况叫做并发执行,但物理上是串行

多核CPU如何实现“多任务”呢?
真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核 ⼼上执⾏。
多进程编程
编写完毕的代码,在没有运⾏的时候,称之为程序
正在运⾏着的代码,就成为进程
注意: 进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的
例:firefox就是写好的程序放在witch firefox里面,当执行firefox时,就编程了进程
进程的五状态模型
创建(created)-----就绪(ready)-----运行(running)-----阻塞(waiting)-----结束(terminated)
创建子进程
Python的os模块封装了常⻅的系统调⽤,其中就包括fork(复制),可以在Python程 序中轻松创建⼦进程:
- 执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中
- 普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次
- ⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号
import os
import time
print('当前进程的pid:',os.getpid()) # os.getpid()查看当前进程的id号
print('当前进程的父进程pid:',os.getppid()) # 查看当前进程父进程的id号
# time.sleep(50)
pid = os.fork()
if pid == 0:
print('子进程返回的信息')
else:
print('创建子进程%s,父进程是%d' %(pid,os.getppid()))
time.sleep(50)
多进程修改全局变量
多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,互不影响,因为创建子进程的过程中子进程将父进程的内存全部复制一份,父进程和子进程的数据空间时相互独立,互不影响的。
多进程编程
Windows没有fork调⽤,由于Python是跨平台的, multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了⼀个Process类来代表⼀个进程对象
Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调⽤对象;
args:表示调⽤对象的位置参数元组;
kwargs:表示调⽤对象的关键字参数字典;
name:为当前进程实例的别名;
group:⼤多数情况下⽤不到;
Process类常⽤⽅法:
is_alive(): 判断进程实例是否还在执⾏;
join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
start(): 启动进程实例(创建⼦进程);
run(): 如果没有给定target参数,对这个对象调⽤start()⽅法时,
就将执 ⾏对象中的run()⽅法;
terminate(): 不管任务是否完成,⽴即终⽌;
Process类常⽤属性:
name:当前进程实例别名,默认Process-N,N为从1开始计数;
pid:当前进程实例的PID值;
多进程编程方法一:实例化对象
框架:

import time
from multiprocessing import Process
def task1():
print('正在听音乐。。。')
time.sleep(1)
def task2():
print('正在敲代码。。。')
time.sleep(2)
def no_multi():
task1()
task2()
def use_multi():
p1 = Process(target=task1)
p2 = Process(target=task2) # 执行的任务
p1.start()
p2.start()
p1.join()
p2.join() # 阻塞等到子进程执行完再执行接下来的程序
if __name__ == '__main__':
start_time = time.time()
# no_multi()
use_multi()
end_time = time.time()
print(end_time-start_time)
使用多进程时间为:3.0024447441101074
没有使用多进程的时间为:2.0092406272888184
import time
from multiprocessing import Process
def task1():
print('正在听音乐。。。')
time.sleep(1)
def task2():
print('正在敲代码。。。')
time.sleep(2)
def no_multi():
for i in range(2):
task1()
for i in range(3):
task2()
def use_multi():
processes = []
for i in range(2):
p1 = Process(target=task1,name='yw')
p1.start()
processes.append(p1)
for i in range(3):
p2 = Process(target=task2)
p2.start()
processes.append(p2)
[p.join() for p in processes]
if __name__ == '__main__':
start_time = time.time()
# no_multi()
use_multi()
end_time = time.time()
print(end_time-start_time)
多进程编程方法二:创建子类、继承
# 通过创建子类,继承的方法实现多进程
import time
from multiprocessing import Process
class MyProcess(Process):
"""
创建自己的进程,父类是Process
"""
# 当任务启动p.start()====>系统会自动寻找run方法p.run()内容是你要执行的任务
def run(self):
"""重写run方法,你要执行的任务"""
for i in range(10):
print('听音乐%d。。。'%i)
time.sleep(1)
if __name__ == '__main__':
# p = MyProcess()
# p.start()
for i in range(10):
p = MyProcess() # 不需要指定任务
p.start()
进程池的使用
为什么需要进程池Pool?
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。

进程池框架:

多进程判断素数:
# 通过实例化对象实现多进程
import time
from multiprocessing import Process
def is_prime(num):
# 根据官方文档的解释理解的意思:当迭代的对象迭代完并为空时,位于else的语句将会执行,而如果在for循环里有break时,则会直接终止循环,并不会执行else里的代码
if num==1:
return False
else:
for i in range(2,num):
if num%i == 0:
return False
else:
return True
def task(num):
if is_prime(num):
print('%d是素数' %num)
def use_multi():# 不要开启太多进程因为创建子进程会耗费太多的时间和空间,反而效果不好
ps = []
for i in range(1,10000):
# 创建进程对象,args=()传递的是任务的参数,以元组的方式
p = Process(target=task,args=(i,))
# 开启子进程
p.start()
# 存储所有的子进程对象
ps.append(p)
# 阻塞子进程,等待所有子进程执行结束,再执行主进程
[p.join() for p in ps]
def no_multi():
for i in range(1,10000):
task(i)
# 实现进程池
def use_pool():
from multiprocessing import cpu_count # cpu个数是4
from multiprocessing import Pool
p = Pool(cpu_count()) # 返回一个进程池对象
p.map(task,list(range(1,10000)))
p.close() # 关闭进程池
p.join() # 阻塞,等待所有的子进程执行结束,再执行主进程
if __name__ == '__main__':
# print(is_prime(1))
start_time = time.time()
#no_multi() 0.4509851932525635
# use_multi() # 开启进程太多没有意义,拷贝父进程的过程浪费时间和空间,解决方法使用进程池
#use_pool() 0.32606935501098633
end_time = time.time()
print(end_time-start_time)
进程间通信
目的:

方式:

消息队列队列:先进先出,
可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递,Queue 本身是⼀个消息列队程序。
Queue.qsize(): 返回当前队列包含的消息数量;
Queue.empty(): 如果队列为空,返回True,反之False ;
Queue.full(): 如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]): block:阻塞等待
获取队列中的⼀条消息,然后将其从列队中移除,block默认值为True;
Queue.get_nowait():
相当Queue.get(False);
Queue.put(item,[block[, timeout]]):
将item消息写⼊队列,block默认值 为True;
Queue.put_nowait(item):
相当Queue.put(item, False)
import time
import multiprocessing
class Producer(multiprocessing.Process):
def __init__(self,queue):
super(Producer, self).__init__()
self.queue = queue
def run(self):
# 将需要通信的数据写入队列中;
for i in range(10):
self.queue.put(i)
time.sleep(1)
print('传递消息,内容为%s' %(i))
class Consumer(multiprocessing.Process):
def __init__(self,queue):
super(Consumer, self).__init__()
self.queue = queue
def run(self):
while True:
time.sleep(0)
recvData = self.queue.get()
print('接收到另一进程传递的数据:%s' %(recvData))
if __name__ == '__main__':
q = multiprocessing.Queue()
p1 = Producer(q)
c1 = Consumer(q)
p1.start()
c1.start()
p1.join()
c1.join()
多线程编程
什么是线程?
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位

每个进程至少有一个线程,即进程本身。进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。
每个进程里面至少有一个线程,我们把它叫做主线程,用来管理其他线程的分配调度。


线程和进程各自有什么区别和优劣呢?
- 进程是资源分配的最小单位,线程是程序执行的最小单位。
- 进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间.
- 进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。
线程分类
有两种不同的线程:
- 内核线程
- 用户空间线程或用户线程
内核线程是操作系统的一部分,而内核中没有实现用户空间线程。
实现多线程编程 方法一:
python的thread模块是⽐较底层的模块,python的threading 模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤
python2 有thread
python3 中变成了_thread
- 多线程程序的执⾏顺序是不确定的。
- 当执⾏到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进⼊就绪(Runnable)状态,等待调度。⽽线程调度将⾃⾏选择⼀个线程执⾏。
- 代码中只能保证每个线程都运⾏完整个run函数,但是线程的启动顺序、 run函数中每次循环的执⾏顺序都不能确定。
import time
import threading
def task():
"""
当前要执行的任务
:return:
"""
print('听音乐。。。')
time.sleep(1)
if __name__ == '__main__':
start_time = time.time()
threads = []
for count in range(5):
t = threading.Thread(target=task)
t.start()
threads.append(t)
[thread.join() for thread in threads]
end_time = time.time()
print(end_time-start_time)
项目案例:IP地址归属地批量查询任务
需求:获取指定IP所在城市和国家并存储到数据库
import requests
import sqlalchemy
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
import pymysql
pymysql.install_as_MySQLdb()
from sqlalchemy.orm import Session, sessionmaker
from threading import Thread, Lock
def task(ip):
# 获取指定ip的所在城市和国家并存储到数据库
# lock.acquire()
url = "http://ip-api.com/json/%s" %(ip)
# 获取网址的返回内容
try:
response = requests.get(url) # 获取指定网址的内容
except Exception as e:
print('网页获取失败。。',e)
else:
page = response.text # 获取文本信息
import json
# 将页面的json字符串转换成便于处理的字典
dict_data = json.loads(page) # 将json字符串转化为字典
city = dict_data.get('city','null') # 如果不指定'null'默认返回的是None对象,无法存储到数据库
country = dict_data.get('country','null')
# 存储到数据库表ips中
ipobj = IP(ip=ip,city=city,country=country)
session.add(ipobj)
session.commit()
# lock.release()
if __name__ == '__main__':
engine = sqlalchemy.create_engine("mysql://root:westos@localhost/Blog",
encoding='utf8', echo=True)# 连接数据库
session = sessionmaker(bind=engine)() # 创建缓存
# 3). 创建数据库对象需要继承的基类
Base = declarative_base()
# 4). 创建类, 一个类就是一个数据库表
class IP(Base):
# 数据库表的名称
__tablename__ = 'ips'
# 数据库表的属性信息
id = Column(Integer, primary_key=True, autoincrement=True)
ip = Column(String(20),nullable=False) # 不能为空
city = Column(String(20))
country = Column(String(20))
def __repr__(self):
return self.ip
# 根据设置的信息创建数据库表-(创建类:创建数据库表的过程)
Base.metadata.create_all(engine)
# lock = Lock()
threads = []
for item in range(1,11):
ip = '1.1.1.'+str(item)
thread = Thread(target=task,args=(ip,))
thread.start()
# thread.join()
# 存储所有线程对象
threads.append(thread)
[thread.join() for thread in threads]
print('任务执行结束。。。')
print(session.query(IP).all())
线程的几种状态:

项目案例二:基于多线程的批量主机存活探测
项目描述: 如果要在本地网络中确定哪些地址处于活动状态或哪些计算机处于活动状态,则可以使用此脚本。我们将依次ping地址, 每次都要等几秒钟才能返回值。这可以在Python中编程,在IP地址的地址范围内有一个for循环和一个os.popen(“ping -q -c2”+ ip)。
os.system(‘ping’) # 执行命令行语句,返回值为0说明命令执行成功,否则执行失败
ping -c 1(1次) -w 1(1秒) 172.25.254.15
ping -c 1 -w 1 172.25.254.15 &> /etc/null# 表示将输出的信息隐藏起来,放入垃圾箱
echo $? 返回1说明执行失败,0说明执行成功
from threading import Thread
class GetHostAcitveThread(Thread):
"""
创建子线程,执行的任务
"""
def __init__(self,ip):
super(GetHostAcitveThread,self).__init__()
self.ip = ip
def run(self):
# 重写run方法:判断ip是否存活
import os
cmd = 'ping -c1 -w1 %s &> /dev/null' %(self.ip)
result = os.system(cmd)
if result != 0:
print('%s主机没有ping通' %(self.ip))
if __name__ == '__main__':
print('打印172.25.254.0网段没有使用的ip地址'.center(50,'*'))
for i in range(1,255):
ip = '172.25.254.' + str(i)
thread = GetHostAcitveThread(ip)
thread.start()
共享全局变量
优点: 在⼀个进程内的所有线程共享全局变量,能够在不使⽤其他⽅式的前提 下完成多线程之间的数据共享(这点要⽐多进程要好)
缺点: 线程是对全局变量随意遂改可能造成多线程之间对全局变量 的混乱(即线程⾮安全)
如何解决线程不安全问题?
全局解释器锁:
GIL(global interpreter lock): python解释器中任意时刻都只有一个线程在执行;
Cpython中默认有全局解释器锁,Jpython中默认没有
多进程+协程也可以实现多任务同时执行
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。


线程同步
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作.
同步就是协同步调,按预定的先后次序进⾏运⾏。如:你说完,我再说。
"同"字从字⾯上容易理解为⼀起动作 其实不是,
"同"字应是指协同、协助、互相配合。

from threading import Thread, Lock
money = 0
def add():
for i in range(1000000):
lock.acquire()
global money
money += 1
lock.release()
def reduce():
for i in range(1000000):
lock.acquire()
global money
money -= 1
lock.release()
if __name__ == '__main__':
lock = Lock() # 创建线程锁,保证同一时刻只有个一线程能够对内存操作
l1 = Thread(target=add)
l2 = Thread(target=reduce)
l1.start()
l2.start()
l1.join()
l2.join()
print(money)
死锁
在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时 等待对⽅的资源,就会造成死锁。

协程
协程,又称微线程,纤程。英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

协程优势:
- 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,
- 没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。
方法一: yield实现


方法二: gevent实现协程
基本思想:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

总结


846

被折叠的 条评论
为什么被折叠?



