多任务编程(Python语言实现)

多任务编程

现实生活中的多任务:
有很多的场景中的事情是同时进行的,比如开车的时候,手和脚共同来驾驶汽车。再比如唱歌和跳舞也是同时进行的。

什么叫"多任务"?
就是操作系统可以同时运行多个人物。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有三个任务正在进行。还有很多任务悄悄的正在后台同时运行着,只是桌面上没有现实而已。

单核CPU如何实现"多任务"呢?
操作系统轮流让各个任务交替执行,每个任务执行0.01秒,这样反复执行下去。表面上看,每个任务交替执行,但CPU的执行速度实在是太快了,感觉就像所有任务都在同时执行一样。
在这里插入图片描述
多核CPU如何实现"多任务"呢?

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多与CPU的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行。
在这里插入图片描述

多进程编程

进程的创建

编写完毕的代码,在没有运行的时候,称之为程序
正在运行着的代码,就成为进程
注意:进程,除了包含代码以外,还需要运行的环境等,所以和程序是由区别的

在这里插入图片描述
创建子进程
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:
在这里插入图片描述
Python中创建子进程的常用函数:

"""
os.fork()
os.getpid()   #获取当前进程的pid (process id)
os.getppid()	#获取当前进程的父进程pid(parent process id)
"""
import os
print("当前进程(pid = %d)正在运行......" %(os.getpid()))
#在pycharm编写代码,程序的父进程就是pycharm;
print("当前进程的父进程为(pid=%d)正在运行....." %(os.getppid()))
print("开始创建子进程.....")

pid = os.fork()
if pid == 0;
	print('这是子进程返回的是0,子进程的pid为%d,父进程为%d' %(os.getpid(),os.getppid()))
else:
	pring("这是父进程返回的,返回值为子进程的pid,为%d" %(pid))

fork函数的作用:
1.执行到os.fork()时,操作系统会创建一个新的进程复制父进程的所有信息到子进程中。
2.普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次。
3.父进程和子进程都会从fork()函数中得到一个返回值,子进程返回的是0,而父进程中返回子进程的id号。

多进程修改全局变量
多进程中,每个进程中所有数据(包括全局变量)都各拥有一份,互不影响。

由于Windows没有fork调用,由于Python是跨平台的,multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Processing模块提供了一个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值;

多进程编程方法一:实例化对象

from multiprocessing import Process
for i in range(len(expression_list)):
	p = Process(target=eval_formula,args=(expression_list[i],))
	p.start()
	p.join()
def eval_formula(formula):

示例1:

from multiprocessing import Process
import time
def task1(): #定义第一个任务
    print('正在听音乐')
    time.sleep(1) #运行过后休眠1s
def task2(): #定义第二个任务
    print('正在编程。。。。')
    time.sleep(0.5) #运行完成休眠0.5s
def no_multi(): #不使用多进程编程运行的方法
    for i in range(2):
        task1()
    for i in range(5):
        task2()
def use_multi(): #使用多进程编程使程序运行
    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()
    use_multi() #调用多进程方法,查看多进程运行所需时间
    end_time = time.time()
print(end_time-start_time)

在这里插入图片描述
以上为多进程编程运行所需要的时间,如果单进程的话时间应该为21+50.5=4.5s

多进程编程方法2

改写run函数:

在这里插入图片描述

进程池

为什么需要进程池Pool?
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。

进程池实现1:

import multiprocessing
p = multiprocessing.Pool(multiprocessing.cpu_count())
result = p.map(ecal_formula,expression_list)
p.close()
p.join()

def eval_formual(formula):
	#evalueates the expression

进程池实现2:

import multiprocessing
def job(id):

    print("start %d...." % (id))

    print("end %d...." % (id))
# 创建进程池对象
pool = multiprocessing.Pool(processes=4)
# 给进程池分配任务;
for i in range(10):
    pool.apply_async(job, args=(i + 1,))
pool.close()
# 等待所有的子进程执行结束, 关闭进程池对象;
pool.join()
print("所有任务执行结束.....")

进程池实现3:

from concurrent.futures import ProcessPoolExecutor
def job(id):
	print('start %d...' % (id)')
	print("end %d..." % (id))
pool = ProcessPoolExecutor(max_workers = 4)
#
#for id in ranger(10):
#	#分配任务给子进程,并且返回一个Future对象;
# 		f1 = pool.submit(job,args=(id))
# 	#判断子进程是否执行结束
#		print(f1.done())
#	#查看该子进程执行的结果
#		print(f1,range(10))

进程间通信

为什么需要进程之间的通信?
在这里插入图片描述
如何实现进程间的通信?
在这里插入图片描述
消息队列
可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递,Queue 本身是⼀个消息列队程序。
Queue.qsize(): 返回当前队列包含的消息数量;
Queue.empty(): 如果队列为空,返回True,反之False ;
Queue.full(): 如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]):
获取队列中的⼀条消息,然后将其从列队中移除,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(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 = Producer(q)
    c1 = Consumer(q)

    p1.start()
    c1.start()
    p1.join()
    c1.join()

输出结果与队列相似,先传入的先传出,后传入的后传出。

多线程编程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
在这里插入图片描述
每个进程至少有一个线程,即进程本身。进程可以启动多个线程。操作系统像并行“进程”一样执行这些线程。
在这里插入图片描述
线程和进程各自有什么区别和优劣呢?
进程是资源分配的最小单位,线程是程序执行的最小单位。
进程有自己的独立地址空间。线程是共享进程中的数据的,使用相同的地址空间.
进程之间的通信需要以通信的方式(IPC)进行。线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,难点:处理好同步与互斥。

线程分类
有两种不同的线程:
内核线程
用户空间线程或用户线程
内核线程是操作系统的一部分,而内核中没有实现用户空间线程。

多线程编程实现
python的thread模块是⽐较底层的模块,python的threading 模块是对thread做了⼀些包装的,可以更加⽅便的被使⽤
在这里插入图片描述
方法分析
多线程程序的执⾏顺序是不确定的。
当执⾏到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进⼊就绪(Runnable)状态,等待调度。⽽线程调度将⾃⾏选择⼀个线程执⾏。
代码中只能保证每个线程都运⾏完整个run函数,但是线程的启动顺序、 run函数中每次循环的执⾏顺序都不能确定。

在这里插入图片描述

项目案例: IP地址归属地批量查询任务

地址处于活动状态或哪些计算机处于活动状态,则可以使用此脚本。我们将依次ping地址, 每次都要等几秒钟才能返回值。这可以在Python中编程,在IP地址的地址范围内有一个for循环和一个os.popen(“ping -q -c2”+ ip)。

项目瓶颈: 没有线程的解决方案效率非常低,因为脚本必须等待每次ping。

非多线程版本代码:

import json
import requests
import pymysql

conn = pymysql.connect(host='172.25.254.149', user='root', password='990038', db='mysql')
#连接数据库
cursor = conn.cursor()
#设置邮标
# create_sqli = 'create table IP(ipid varchar(20),ipcity varchar(20),ipcountry varchar(20));'(数据库建表操作,如果已经存在这一步便没有必要重复进行)
# cursor.execute(create_sqli)


def get_addr(ip):
    url = 'http://ip-api.com/json/%s' % (ip)
    #从上面这个网址获取各ip所在地址
    count = requests.get(url).text
    dict_data = json.loads(count)
    city = dict_data.get('city')
    country = dict_data.get('country')
    #获取到所在城市以及国家的信息
    insert_sqli = 'insert into IP(ipid,ipcity,ipcountry) values("%s", "%s", "%s");' %(ip, city, country)
    #将获取到的信息导入数据库
    print(insert_sqli)
    #将结果打印出来确定是否导入
    cursor.execute(insert_sqli)
    conn.commit()
    # print("""
    # %s
    # 所在城市:%s
    # 所在国家:%s
    #
    # """ % (ip,dict_data['city'],dict_data['country']))


if __name__ == '__main__':
    for i in range(25):
        ip = str('1.1.1.') + str(1 + i)
        get_addr(ip)
        #将1.1.1.1到1.1.1.255的ip信息全部获取并且存入
    cursor.close()
    conn.close()

多线程版本代码(使用sqlalchemy):

import requests
import json
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from threading import Thread


def task(ip):
    """获取指定IP的所在城市和国家并存储到数据库中"""
    # 获取网址的返回内容
    url = 'http://ip-api.com/json/%s' % (ip)
    try:
        response = requests.get(url)
    except Exception as e:
        print("网页获取错误:", e)
    else:
        # 默认返回的是字符串
        """
        {"as":"AS174 Cogent Communications","city":"Beijing","country":"China","countryCode":"CN","isp":"China Unicom Shandong Province network","lat":39.9042,"lon":116.407,"org":"NanJing XinFeng Information Technologies, Inc.","query":"114.114.114.114","region":"BJ","regionName":"Beijing","status":"success","timezone":"Asia/Shanghai","zip":""}
        """
        contentPage = response.text
        # 将页面的json字符串转换成便于处理的字典;
        data_dict = json.loads(contentPage)
        # 获取对应的城市和国家
        city = data_dict.get('city', 'null')  # None
        country = data_dict.get('country', 'null')

        print(ip, city, country)
        # 存储到数据库表中ips
        ipObj = IP(ip=ip, city=city, country=country)
        session.add(ipObj)
        session.commit()


if __name__ == '__main__':
    engine = create_engine("mysql+pymysql://root:westos@172.25.254.123/pymysql",
                           encoding='utf8',
                           # echo=True
                           )
    # 创建缓存对象
    Session = sessionmaker(bind=engine)
    session = Session()

# 声明基类
Base = declarative_base()


class IP(Base):
    __tablename__ = 'ips'
    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


# 创建数据表
Base.metadata.create_all(engine)

# 1.1.1.1 -- 1.1.1.10
threads = []
for item in range(10):
    ip = '1.1.1.' + str(item + 1)  # 1.1.1.1 -1.1.1.10
    # task(ip)
    # 多线程执行任务
    thread = Thread(target=task, args=(ip,))
    # 启动线程并执行任务
    thread.start()
    # 存储创建的所有线程对象;
    threads.append(thread)

[thread.join() for thread in threads]
print("任务执行结束.........")
print(session.query(IP).all())

共享全局变量
优点: 在⼀个进程内的所有线程共享全局变量,能够在不使⽤其他⽅式的前提 下完成多线程之间的数据共享(这点要⽐多进程要好)

缺点: 线程是对全局变量随意遂改可能造成多线程之间对全局变量 的混乱(即线程⾮安全)

享全局变量:如何解决线程不安全问题?
GIL(global interpreter lock): python解释器中任意时刻都只有一个线程在执行;
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

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

线程锁的代码实现。

在这里插入图片描述
银行转账问题:

import threading


def add(lock):
    global money #声明money为全局变量
    for i in range(1000000):
        #2.操作变量之前进行加锁
        lock.acquire()
        money += 1
        lock.release()

def reduce(lock):
    global money
    for i in range(100000):
        #2.操作变量之前进行加锁
        lock.acquire()
        money -= 1
        #3.操作变量之后进行解锁
        lock.release()
if __name__ == '__main__':
    money = 0
    #1.实例化一个锁对象
    lock = threading.Lock()
    t1 = threading.Thread(target=add,args=(lock,))
    t2 = threading.Thread(target=reduce,args=(lock,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('当前金额:',money)

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

协程

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

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

实现协程的方法一(yield方法):
在这里插入图片描述

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

总结

在这里插入图片描述

  1. 线程与进程的区别?
    答:进程为正在运行着的代码,除此之外还有需要运行的环境等。线程是操作系统能够运行的最小单位,他被包含在进程之中是进程中的实际运作单位。
  2. 进程间内存是否共享?如何实现通讯?
    答:可以共享,可以通过进程池以及消息队列的方式进行内存分配。通过消息队列进行通讯,目的为数据传输,共享数据,通知事件,资源共享以及进程控制
  3. 进程间通信的方式?
    答:linux系统进程间的通信方式有管道,信号,消息队列,以及套间字
  4. 多线程有几种实现方法,都是什么?
    答:分别是实例化对象方法,以及创建子类方法。
    5.GIL锁是怎么回事?
    答:当存在全局变量时,每一个线程都要对全局变量进行修改改或者更新时,就还出现错误,所以python中就设置有GIL锁,使得在修改全局变量时只能一个线程能对其进行操作,当一个线程运行完后,下一个线程才能对其进行操作。
  5. python中是否线程安全?如何解决线程安全?
    答:Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
  6. 什么叫死锁?
    答:在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时 等待对⽅的资源,就会造成死锁。
  7. 什么是协程?常用的协程模块有哪些?
  8. 协程中的join是用来做什么用的?它是如何发挥作用的?
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值