一、多任务编程
有很多的场景中的事情是同时进⾏的,⽐如开⻋的时候 ⼿和脚共同来驾驶汽⻋,再⽐如唱歌跳舞也是同时进⾏的。
就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运 ⾏着,只是桌⾯上没有显示⽽已。
操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。
真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核 ⼼上执⾏。
多进程编程
进程的创建
编写完毕的代码,在没有运⾏的时候,称之为程序
正在运⾏着的代码,就成为进程 。
注意: 进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的
进程分为五个状态。
我们实现多进程的方式有很多种。
Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程 序中轻松创建⼦进程:
代码实现如下:
import os
print("当前进程的pid:",os.getpid())
print("当前进程的父进程pid:",os.getppid())
p = os.fork()
if p==0 :
print("子进程返回的信息0,子进程pid:%d,父进程pid:%d"%(os.getpid(),os.getppid()))
else:
print("父进程返回的,返回值为子进程pid,为%d"%p)
执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中
普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次
⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号。
多进程编程方法1: 实例化对象
from multiprocessing import Process
import time
def task1():
print("正在听音乐")
time.sleep(1)
def task2():
print("正在编程。。。")
time.sleep(0.5)
def no_mulit():
for i in range(2):
task1()
for i in range(5):
task2()
def use_mulit():
processes=[]
for i in range(2):
p=Process(target=task1)
p.start()
processes.append(p)
for i in range(5):
p=Process(target=task2)
p.start()
processes.append(p)
[process.join() for process in processes]
if __name__ == '__main__':
start_time=time.time()
# no_mulit()
use_mulit()
end_time=time.time()
print(end_time-start_time)
对于结果来说肯定是多进程的执行快。
看下我们对进程的运行时间:
和没有用多进程的时间:
对比以下就知道多进程的执行效率要快很多。
多进程编程方法2: 创建子类(继承)。
当我们运行程序的时候,start()方法就会去自动寻找我们的run()方法。
from multiprocessing import Process
import time
class MyProcess(Process):
"""
创建自己的进程, 父类是Process
"""
def __init__(self, music_name):
super(MyProcess, self).__init__()
self.music_name = music_name
def run(self):
"""重写run方法, 内容是你要执行的任务"""
print("听音乐%s" %(self.music_name))
time.sleep(1)
if __name__ == '__main__':
for i in range(10):
p = MyProcess("音乐%d" %(i))
p.start()
当然除了这两种方式外,还有更快的方法,就是进程池。
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
from multiprocessing import Process
import time
def is_prime(num):
if num ==1:
return False
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 no_mulit():
for i in range(1,100001):
task(i)
def use_mulit():
processes=[]
for i in range(1000,1201):
p = Process(target=task,args=(i,))
p.start()
processes.append(p)
[process.join() for process in processes]
def use_pool():
from multiprocessing import Pool
from multiprocessing import cpu_count
p=Pool(cpu_count())
p.map(task,list(range(1,100001)))
p.close()
p.join()
if __name__ == '__main__':
start_time=time.time()
# no_mulit()
# use_mulit()
use_pool()
end_time=time.time()
print(end_time-start_time)
进程通信
可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递,Queue 本身是⼀个消息列队程序。
import multiprocessing
import time
class Producen(multiprocessing.Process):
def __init__(self,queue):
super(Producen,self).__init__()
self.queue = queue
def run(self):
for i in range(10):
self.queue.put(i)
time.sleep(0.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.1)
recvData=self.queue.get()
print("接受到另一个进程传递的数据:%s"%(recvData))
if __name__ == '__main__':
q=multiprocessing.Queue()
p1=Producen(q)
c1=Consumer(q)
p1.start()
c1.start()
p1.join()
c1.join()
多线程编程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
每个进程至少有一个线程,即进程本身。进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。
线程和进程各自有什么区别和优劣呢?
进程是资源分配的最小单位,线程是程序执行的最小单位。
进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间.
进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。
和进程一样,多线程也有两种方式来实现。
python的thread模块是⽐较底层的模块,python的threading 模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤。
通过实例化对象的方式实现多线程
import time
import threading
def task():
"""当前要执行的任务"""
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)
创建子类, 继承的方式
from threading import Thread
class GetHostAliveThread(Thread):
def __init__(self,ip):
super(GetHostAliveThread, self).__init__()
self.ip=ip
def run(self):
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__':
for i in range(1,255):
ip ='172.25.254.'+str(i)
thread=GetHostAliveThread(ip)
thread.start()
同样的线程也拥有多种不同的状态。
多线程案例:判断ip归属地。
我在做这个案例的时候遇见了很多问题,查了很多报错信息。总结了一下解决办法。在使用多线程的时候。我们就要考虑如果所有线程共享同一连接,每个execute之前请加上互斥锁。而且还不会有我们的任务丢失的情况。
lock.acquire()
cursor.execute(command,data)
lock.release()
几种解决方案:
1. 每个线程拥有自己的连接
2. 所有线程共用一个连接,加锁互斥使用此连接
3. 所有线程共用一个连接池,需要考虑线程总数和连接池连接数上限的问题
import requests
import threading
#连接数据库
from sqlalchemy import create_engine,Column,Integer,String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from threading import Thread,Lock
engine=create_engine("mysql+pymysql://root:redhat@172.25.254.139/pymysql",
encoding='utf8',
# echo=True
)
Session =sessionmaker(bind=engine)
session=Session()
Base=declarative_base()
class IP(Base):
__tablename__='Ipinfo'
id =Column(Integer,primary_key=True,autoincrement=True)
ip=Column(String(20),nullable=False)
city=Column(String(30))
country=Column(String(30))
def __repr__(self):
return self.ip
def get_addr(ip):
url = 'http://ip-api.com/json/%s'%ip
page = requests.get(url).text
import json
# dict_data=page
dict_data=json.loads(page)
city=dict_data['city']
country=dict_data['country']
Ipobj= IP(ip=ip, city=city, country=country)
lock.acquire()
session.add(Ipobj)
session.commit()
lock.release()
if __name__ == '__main__':
Base.metadata.create_all(engine)
threads=[]
lock=Lock()
for i in range(0,10):
ip='1.1.1.'+str(i+1)
t=threading.Thread(target=get_addr,args=(ip,))
t.start()
threads.append(t)
[thread.join() for thread in threads]
print("程序运行结束")
这就是我们数据库里查询到的结果。
死锁
在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时 等待对⽅的资源,就会造成死锁。
协程
协程,又称微线程,纤程。英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
协程优势
执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,
没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。