python-多任务

多任务的概念:

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

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

并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的

 

生理过程(从生到死) 创建 -> 就绪 -> 运行 -> 阻塞 -> 死亡

线程和进程的创建一定要在主函数中,且主任务和子任务一起往下执行,遇到join()方法,主任务会等子任务执行完在结束

线程:

线程可以理解成程序中的一个可以执行的分支, 它是cup调度的基本单元。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。

 

from threading import Thread
import time

#1. 如果多个线程执行的都是同一个函数的话,各自之间不会有影响,各是个的
def test(arg):
    time.sleep(1)
    print("thread"+str(arg))


for i in range(5):  #5个线程同时执行
    t = Thread(target=test,args=(i,))
    t.start()


结果:
thread0
thread2
thread1
thread3
thread4


线程代码的封装:

import threading,time

class MyThread(threading.Thread):
    def run(self):
        for i in range(5):
            time.sleep(1)
            print('I`m '+self.name+ '进程 '+str(i)) #name当前的线程名


if __name__=='__main__':
    t=MyThread()
    t.start()



多线程的创建: 

import threading,time


def dance():
    print('子线程1:',threading.current_thread().name)
    while True:
        print('跳舞...')
        time.sleep(1)


def sing():
    print('子线程2:',threading.current_thread().name)
    while True:
        print('在唱歌...')
        time.sleep(1)


def dance1(count):
    print('子线程1:',threading.current_thread().name)
    for i in range(count):
        print('跳舞...')
        time.sleep(1)
    else:
        print('任务完成...')


def sing1(count):
    print('子线程1:',threading.current_thread().name)
    for i in range(count):
        print('跳舞...')
        time.sleep(1)
    else:
        print('任务完成...')



def main():
    """多任务开始"""
    print('main线程:',threading.current_thread().name)
    thread1 = threading.Thread(target=dance1,args=(5,))
    thread2 = threading.Thread(target=sing1,args=(3,))

    thread1.start()  # 开启线程
    thread2.start()


if __name__ == '__main__':
    """单线程----->>多线程
    cpu调度的单位是线程,无序的切换任务来提高效率
    """
    main()

 线程注意点:

1.线程之间执行是无序的

2.主线程会等待所有的子线程结束而结束

3.设置守护线程的目的是当,主线程结束后结束守护线程 

4.如果想要子线程结束以后,主线程再执行相关代码可以使用join,阻塞子线程

5.线程共享全局变量,造成资源竞争数据丢失,解决办法:join(),互斥锁

 

 

 多线程间共享全局变量,造成对共享数据的出错,使用互斥锁,给各个进程上锁,当这个进程释放后,下一个进程才开始执行,依次循环,保护好共享的安全性。

#多线程间共享数据出错:
from threading import Thread
import time


g_num = 0  #全局变量

def test1():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    for i in range(1000000):
        g_num += 1
    print("---test2---g_num=%d"%g_num)


p1 = Thread(target=test1)
p1.start()

#time.sleep(3) #取消屏蔽之后 再次运行程序,数据就不会变化,因为test1进程在3s内足够执行完,不造成数据的错乱。

p2 = Thread(target=test2)
p2.start()

print("---g_num=%d---"%g_num)



结果:
---g_num=234497---
---test1---g_num=1143699
---test2---g_num=1342951

 

#使用互斥锁:
from threading import Thread, Lock
import time

g_num = 0

def test1():
    global g_num
    #这个线程和test2线程都在抢着 对这个锁 进行上锁,如果有一方成功的上锁,那么导致另外一方会堵塞(一直等待)到这个锁被解开为止
    mutex.acquire()
    for i in range(1000000):
        g_num += 1
    mutex.release()
#用来对mutex指向的这个锁 进行解锁,,,只要开了锁,那么接下来会让所有因为这个锁 被上了锁 而堵塞的线程 进行抢着上锁

    print("---test1---g_num=%d"%g_num)

def test2():
    global g_num
    mutex.acquire()
    for i in range(1000000):
        g_num += 1
    mutex.release()

    print("---test2---g_num=%d"%g_num)


#创建一把互斥锁,这个锁默认是没有上锁的
mutex = Lock()

p1 = Thread(target=test1)
p1.start()

#time.sleep(3) #取消屏蔽之后 再次运行程序,结果会不一样,,,为啥呢?

p2 = Thread(target=test2)
p2.start()

print("---g_num=%d---"%g_num)



#互斥锁放到for循环里,最后是200万,第一个都在执行
  • 同步:

同步就是协同步调,按预定的先后次序进行运行如:你说完,我再说。

如进程,线程同步,可理解为进程或线程甲和乙一块配合,A执行到一定程度时要依靠乙的某个结果,于是停下来,示意乙运行;乙依言执行,再将结果给阿; 一个再继续操作。

from threading import Thread,Lock
from time import sleep

class Task1(Thread):
    def run(self):
        while True:
            if lock1.acquire():
                print("------Task 1 -----")
                sleep(0.5)
                lock2.release()

class Task2(Thread):
    def run(self):
        while True:
            if lock2.acquire():
                print("------Task 2 -----")
                sleep(0.5)
                lock3.release()

class Task3(Thread):
    def run(self):
        while True:
            if lock3.acquire():
                print("------Task 3 -----")
                sleep(0.5)
                lock1.release()

#使用Lock创建出的锁默认没有“锁上”
lock1 = Lock()
#创建另外一把锁,并且“锁上”
lock2 = Lock()
lock2.acquire()
#创建另外一把锁,并且“锁上”
lock3 = Lock()
lock3.acquire()

t1 = Task1()
t2 = Task2()
t3 = Task3()

t1.start()
t2.start()
t3.start()


结果:
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----
------Task 1 -----
------Task 2 -----
------Task 3 -----

 蟒的队列模块中提供了同步的,线程安全的队列类,包括FIFO(先入先出)队列队列,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue.Queue可以使用队列来实现线程间的同步。

#队列解耦, - 解决死锁问题

  • 异步:
from multiprocessing import Pool
import time
import os

 #异步的理解:主进程正在做某件事情,突然 来了一件更需要立刻去做的事情,
 #那么这种,在父进程去做某件事情的时候 并不知道是什么时候去做,的模式 就称为异步

def test():
    print("---进程池中的进程---pid=%d,ppid=%d--"%(os.getpid(),os.getppid()))
    for i in range(3):
        print("----%d---"%i)
        time.sleep(1)
    return "hahah"

def test2(args):
    print("---callback func--pid=%d"%os.getpid())
    print("---callback func--args=%s"%args)  #hahah



if __name__ == '__main__':

    pool = Pool(3)
    pool.apply_async(func=test,callback=test2)

    time.sleep(5)
    print("----主进程-pid=%d----"%os.getpid())



结果:
---进程池中的进程---pid=7664,ppid=5612--
----0---
----1---
----2---
---callback func--pid=5612
---callback func--args=hahah
----主进程-pid=5612----
  • GIL

在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。

在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。

GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。

 

python针对不同类型的代码执行效率也是不同的:

1、CPU密集型代码(各种循环处理、计算等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

 

使用建议?

python下想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。

 

 

 

 

 

进程

在Python中进行多任务,通过并行处理任务。正在运行着的代码,成为就进程

一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)

一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间),还要有至少一个线程。

每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。

from multiprocessing import Process
import time,random

def test():
    for i in range(random.randint(1,5)):
        print('-----%d----'%i)
        time.sleep(1)


if __name__ == "__main__":
    p = Process(target=test)
    p.start() #让这个进程开始执行test函数里的代码

    p.join()#堵塞
    print('------main-------')

 多进程的实现:

from multiprocessing import Pool
import os
import random
import time

#多进程的实现-利用进程池实现:
def worker(num):
    for i in range(5):
        print("===pid=%d==num=%d="%(os.getpid(), num))
        time.sleep(1)


if __name__ == '__main__':
    #3表示 进程池中对多有3个进程一起执行
    pool = Pool(3)

    for i in range(10):
        print("---%d---"%i)
        #向进程池中添加任务
        #注意:如果添加的任务数量超过了 进程池中进程的个数的话,那么不会导致添加不进入
        #       添加到进程中的任务 如果还没有被执行的话,那么此时 他们会等待进程池中的
        #       进程完成一个任务之后,会自动的去用刚刚的那个进程 完成当前的新任务
        pool.apply_async(worker, (i,))  #非堵塞的方式


    pool.close()#关闭进程池,相当于 不能够再次添加新任务了
    pool.join()#主进程 创建/添加 任务后,主进程 默认不会等待进程池中的任务执行完后才结束
                #而是 当主进程的任务做完之后 立马结束,,,如果这个地方没join,会导致
                #进程池中的任务不会执行

#方法1:
#p1=Process(target=xxx)
#p1.start()


#方法2:
# pool=Pool(3)
# pool.apply_async(xxxx)

进程间的通讯:

from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('Get %s from queue.' % value)
            time.sleep(random.random())
        else:
            break

if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 等待pw结束:
    pw.join()

    # 启动子进程pr,读取:
    pr.start()
    pr.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    print('')
    print('所有数据都写入并且读完')


#进程池间的通讯:
#如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue()

协程

线程和进程的操作是由程序触发系统接口,最后的执行者是系统,它本质上是操作系统提供的功能。而协程的操作则是程序员指定的,在python中通过yield,人为的实现并发处理。

协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时。协程,则只使用一个线程,分解一个线程成为多个“微线程”,在一个线程中规定某个代码块的执行顺序。

协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)。
常用第三方模块gevent和greenlet。(本质上,gevent是对greenlet的高级封装,因此一般用它就行,这是一个相当高效的模块。)

from gevent import monkey; monkey.patch_all()
import gevent
import requests

def f(url):
    print('GET: %s' % url)
    resp = requests.get(url)
    data = resp.text
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
])

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值