一、多任务编程

有很多的场景中的事情是同时进⾏的,⽐如开⻋的时候 ⼿和脚共同来驾驶汽⻋,再⽐如唱歌跳舞也是同时进⾏的。
就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运 ⾏着,只是桌⾯上没有显示⽽已。

操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。

真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核 ⼼上执⾏。

dophinscheduler的python任务 python多任务编程_多进程

多进程编程

进程的创建

编写完毕的代码,在没有运⾏的时候,称之为程序
正在运⾏着的代码,就成为进程 。

注意: 进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的

进程分为五个状态。

dophinscheduler的python任务 python多任务编程_多进程_02


我们实现多进程的方式有很多种。

Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程 序中轻松创建⼦进程:

dophinscheduler的python任务 python多任务编程_ci_03


代码实现如下:

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)

dophinscheduler的python任务 python多任务编程_多线程_04


执⾏到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)

对于结果来说肯定是多进程的执行快。

看下我们对进程的运行时间:

dophinscheduler的python任务 python多任务编程_多进程_05


和没有用多进程的时间:

dophinscheduler的python任务 python多任务编程_多线程_06


对比以下就知道多进程的执行效率要快很多。

多进程编程方法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)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

dophinscheduler的python任务 python多任务编程_多进程_07

每个进程至少有一个线程,即进程本身。进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。

线程和进程各自有什么区别和优劣呢?

进程是资源分配的最小单位,线程是程序执行的最小单位。
进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间.
进程之间的通信需要以通信的方式(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()

同样的线程也拥有多种不同的状态。

dophinscheduler的python任务 python多任务编程_多进程_08


多线程案例:判断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("程序运行结束")

这就是我们数据库里查询到的结果。

dophinscheduler的python任务 python多任务编程_ci_09

死锁

在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时 等待对⽅的资源,就会造成死锁。

协程

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

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