• 线程、进程和协程


    概念

    多线程和多进程的区别:
        进程之间不共享内存,线程之间可以共享内存。多进程可以利用多颗cpu,多线程不行。
    cpu全局解释器锁(GIL):
        在每个进程的出口,多个线程任务请求cpu调度,只有一个线程能够穿过,所以单个进程不管有多少线程只能调度一个cpu。
    多线程、多进程如何选择:
        计算密集型应用:能够利用多颗cpu的性能,采用多进程;

        IO密集型应用:大量的IO操作,调度一颗cpu足以,采用多线程。

    主线程:

       程序从上往下执行,解释器的执行过程,叫主线程。

    多线程

    一个简单的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import threading
    import time
     
    lock=threading.Lock()       #加锁防争抢屏幕输出
    def run(num):
        lock.acquire()
    print "thread......" ,num
        lock.release()
        time.sleep(1)
     
    for i in range(10):
        t = threading.Thread(target=run,args=(i,))  #实例化,i后面必须有逗号,每创建一个线程,执行run函数。相当于把i当做num传给run().
        t.start()   #执行start方法
         上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。
    更多方法:
    start     线程准备就绪,等待CPU调度  
    setName 为线程设置名称  
    getName       获取线程名称  
    setDaemon

    设置为前台线程(默认)后台线程

    如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

    如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止

    t=thread.Thread......

    t.setDeamon(Ture or False)

    t.start()

    join 逐个执行每个线程,执行完毕后继续往下执行...就是先把其它前台线程都执行完了才去执行主线程 t.start()
    t.join()
        #执行到这儿后返回执行其它线程,可以有参数,比如加个2,就是等2s,2s上面的线程没执行完,就往下执行主线程了。
    run 线程被cpu调度后执行此方法  

    线程锁:

    如果有一个变量,每个线程获取它后都+=1,那么这时就会出现抢占,而且每个线程拿到的变量数据不一样,有可能是计算之前的,也有可能是计算之后的,最终影响了最后的计算结果。

    未加线程锁的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import threading
    import time
     
    gl_num = 0
     
    def show(arg):
        global gl_num   #声明为全局变量
        time.sleep(1)
        gl_num +=1      #1、gl_num=0+1
        print gl_num
     
    for i in range(10): #创建10个线程
        t = threading.Thread(target=show, args=(i,))
        t.start()
     
    print '主线程已执行完毕。'
    运行结果:
    1
    2
    3
    4
    12
    34567
    8
    99

    可以看到,数字都是随机的,并没有得到预想的结果。

    加入线程锁:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import threading
    import time
     
    gl_num = 0
        
    lock = threading.RLock()    #RLock是递归,Lock是非递归
     
    def Func():
        lock.acquire()  #当某一个线程拿到这个变量就锁上了,别人不能碰
        global gl_num
        gl_num +=1      #第一个线程:gl_num=0+1,第二个:gl_num=1+1,第三个:。。。。。。
        time.sleep(1)
        print gl_num
        lock.release()  #计算完成,释放锁,下一个线程可以去拿了
     
    for i in range(10):
        t = threading.Thread(target=Func)
        t.start()
    运行结果:
     
    1
    2
    3
    4
    主线程已执行完毕。
    1
    2
    3

    加入了线程锁的程序结果正确了,注意结果的打印顺序,setDaemon是默认的True,所以主线程执行完毕后,等待前台线程也执行完成后,程序停止。

    event:

    setDaemon的作用主要是主线程是否等待其他线程,而这个event(python线程的事件)就可以控制其它的线程,通过设定标志位控制子线程何时执行,它(通过三个方法:set、wait、clear)能让子线程停下来,也能够让子线程继续。

    三个方法,一个字段

    三个方法:set、wait、clear

    事件处理的机制:

    在event内部定义了一个标志位“Flag”,这个“Flag”你看不见,当:

    1. 如果“Flag”值为 False(执行event_obj.clear()设定),那么当程序执行 event.wait( )时就会阻塞;

    2. 如果“Flag”值为True(执行event_obj.set()设定,那么event.wait 方法时便不再阻塞。

    代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import threading
      
    def do(event):
        print 'start'
        event.wait()
        print 'execute'
      
    event_obj = threading.Event()
    for i in range(10):
        t = threading.Thread(target=do, args=(event_obj,))
        t.start()
      
    event_obj.clear()
    inp = raw_input('input:')
    if inp == 'true':
        event_obj.set()

    使用event,要先实例化,创建event_obj,然后才能执行它里面的三个方法set、wait、clear

    clear( ):将“Flag”设置为False

    set( ):将“Flag”设置为True

    执行结果:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    start
    start
    start
    ……
     input:true
    execute
    execute
    execute
    ……

    开始,主线程先把标志位设定为False,每个子线程执行了打印“start”任务,然后因为event.wait()就阻塞住了,直到input:true,标志位设定成了True,下面的打印“execute”任务才会被执行。

    多进程

    1
    2
    3
    4
    5
    6
    7
    8
    from multiprocessing import Process
     
    def foo(i):
        print 'say hi',i
     
    for i in range(10):
        p = Process(target=foo,args=(i,))
        p.start()

    和创建多线程代码类似,只不过导入的模块不一样而已。

    创建和cpu-cores相等的进程,能最大化利用cpu,也是最合理的数量。

    多进程因为内存不能共享,所以创建进程需要非常大的系统资源开销。数据怎么才能共享呢?

    进程间数据共享:

    1、用一个特殊的数据结构,Array数组,声明后,其它进程能使用了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #方法一,公共数组Array
    from multiprocessing import Process,Array
    temp = Array('i', [11,22,33,44])
      
    def Foo(i):
        temp[i] = 100+i
        for item in temp:
            print i,'----->',item
      
    for i in range(2):
        p = Process(target=Foo,args=(i,))
        p.start()

    2、公共数据字典manage.dict()

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #方法二:manage.dict()共享数据
    from multiprocessing import Process,Manager
      
    manage = Manager()
    dic = manage.dict()
      
    def Foo(i):
        dic[i] = 100+i
        print dic.values()
      
    for i in range(2):
        p = Process(target=Foo,args=(i,))
        p.start()
        p.join()

    既然进程之间数据能够共享,那么势必会造成争抢,形成脏数据,so,和进程一样,要加一个锁。

    进程锁:

    进程锁的使用方法和线程锁一样,只不过调用的方法不同而已。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from multiprocessing import Process, Array, RLock
     
    def Foo(lock,temp,i):
        """
        将第0个数加100
        """
        lock.acquire()
        temp[0] = 100+i
        for item in temp:
            print i,'----->',item
        lock.release()
     
    lock = RLock()
    temp = Array('i', [11, 22, 33, 44])
     
    for i in range(20):
        p = Process(target=Foo,args=(lock,temp,i,))
        p.start()

    进程池:

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

    创建进程池代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from  multiprocessing import Process,Pool
    import time
     
    def Foo(i):
        time.sleep(2)
        return i+100
     
    def Bar(arg):   #arg是Foo函数的返回值
        print arg
     
    pool = Pool(5#创建5个池进程
    #Pool类怎么来的?from multiprocessing导入的时候,加载了__init__.py中的Pool函数,这个函数又把pool.py中的class Pool加载进内存
     
    print pool.apply(Foo,(1,))  #同步模式,一个一个执行
     
    print pool.apply_async(func =Foo, args=(1,)).get()  #异步模式,同时执行所有进程
     
    for i in range(10): #进程池只有5个进程连接,所以会5个5个执行
        pool.apply_async(func=Foo, args=(i,),callback=Bar)  #执行完func再执行callback
     
    print 'end'
    pool.close()    #不再接受新的请求
    #pool.terminate()    #立即关闭,不管执行完没有,如果写了这句,后面的join就没有意义了。
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
    执行结果:

    注意:在windows上执行要加入 if __name__=="__main__": ,否则会报错。

            The "freeze_support()" line can be omitted if the program

                        is not going to be frozen to produce a Windows executable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@localhost ]# python process_pool.py
    101
    101
    end
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109

     

    函数解释:
    • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的;

    • close()    关闭pool,使其不在接受新的任务。

    • terminate()    结束工作进程,不再处理未完成的任务。

    • join()    主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

    部分来源:http://www.cnblogs.com/kaituorensheng/p/4465768.html

    协程

    相关概念:

    定义:

    对线程的一个分片

    与进程、线程的区别:

    线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员

    存在的意义:

    对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(上下文切换开销,保存状态,下次继续)。

    协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序,效率更高

    适用场景:

    当程序中存在大量不需要CPU的操作时(IO),适用于协程;如,网络爬虫。

    协程代码:greenlet(需要安装,用处不大。

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 参考《python安装.egg文件》安装
     
    from greenlet import greenlet
     
    def test1():
        print 1
        gr2.switch()    #切换到执行test2函数,并记录执行到这里的标记,下次再switch回来的时候,从这里接着执行。
        print 2
        gr2.switch()
     
    def test2():
        print 3
        gr1.switch()
        print 4
     
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()        #和yield相似,swithch()的时候先暂停,执行下一个。
    执行结果:
     
    1
    2
    3
    4
    1
    3
    2
    4

    greenlet是主动切换,当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

    由于IO操作非常耗时,经常使程序处于等待状态,第三方的gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

    genvent:

    genvent是对greenlet的一个封装,同时发多个IO请求,不阻塞。

    由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from gevent import monkey; monkey.patch_all()    
    import gevent
    import urllib2
     
    def f(url):
        print('GET: %s' % url)
        resp = urllib2.urlopen(url)
        data = resp.read()
        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/'),
    ])
    运行结果:

     

    1
    2
    3
    4
    5
    6
    GET: https://www.python.org/
    GET: https://www.yahoo.com/
    GET: https://github.com/
    45661 bytes received from https://www.python.org/.
    14823 bytes received from https://github.com/.
    304034 bytes received from https://www.yahoo.com/.

    从结果看,3个网络操作是并发执行的,而且结束顺序不同,但只有一个线程。

    部分来源:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001407503089986d175822da68d4d6685fbe849a0e0ca35000

     





  • 相关阅读:
    MySQL同步故障:" Slave_SQL_Running:No" 两种解决办法
    mysql运维-slave_skip_errors
    linux 的mysql 主从备份
    linux 查看和设置主机名
    mysql连接数
    红帽6.9搭建yum源的2种方式(HTTP和本地)
    linux 重启服务器命令
    Linux开机启动顺序启动顺序及配置开机启动
    在Windows Server 2012的Task Scheduler里面配置自动发送邮件
    "Cannot find one of more components. Please reinstall the application"--安装VS2013之后不能正常打开的处理办法
  • 原文地址:https://www.cnblogs.com/daliangtou/p/5103442.html
Copyright © 2020-2023  润新知