zoukankan      html  css  js  c++  java
  • Python基础-进程和线程

    一、进程和线程的概念

    首先,引出“多任务”的概念:多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务。Linux、windows就是支持多任务的操作系统,比起单任务系统它的功能增强了许多。

    例如,你一边在用浏览器上网,一边在听网易云音乐,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

    但是,这些任务是同时在运行着的吗?众所周知,运行一个任务就需要cpu去处理,那同时运行多个任务就必须需要多个cpu?那如果有100个任务需要同时运行,就得买一个100核的cpu吗?显然不能!

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

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

    小结:一个cpu同一时刻只能运行一个“任务”;真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

    对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

    有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

    由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现

    小结:

    • 进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。
    • 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。

    二、进程和线程的关系

    进程是计算机中的程序关于某数据集上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
    线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

    小结:

    • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
    • CPU分给线程,即真正在CPU上运行的是线程。

    三、并行(xing)和并发

    并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。

    并发处理(concurrency Processing)指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行。

    并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集。

    四、同步与异步

    在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。

    异步是指进程不需要一直等下去,而是继续执行其它操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。举个例子,打电话时就是同步通信,发短息时就是异步通信。

    举个例子:

    由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,有两种办法解决:

    1. CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO
    2. CPU不等待,只是告诉磁盘,慢慢写不着急,写完通知我,我接着干别的事去了,于是后续代码可以接着执行,这种模式称为异步IO

    五、threading模块

    线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。

    Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

    1. 调用Thread类直接创建

    启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

     1 import time, threading
     2 
     3 # 新线程执行的代码:
     4 def loop():
     5     print('thread %s is running...' % threading.current_thread().name)
     6     n = 0
     7     while n < 5:
     8         n = n + 1
     9         print('thread %s >>> %s' % (threading.current_thread().name, n))
    10         time.sleep(1)
    11     print('thread %s ended.' % threading.current_thread().name)
    12 
    13 print('thread %s is running...' % threading.current_thread().name)
    14 t = threading.Thread(target=loop, name='LoopThread')
    15 t.start()
    16 t.join()
    17 print('thread %s ended.' % threading.current_thread().name)
    18 
    19 
    20 #运行结果:
    21 #thread MainThread is running...
    22 # thread LoopThread is running...
    23 # thread LoopThread >>> 1
    24 # thread LoopThread >>> 2
    25 # thread LoopThread >>> 3
    26 # thread LoopThread >>> 4
    27 # thread LoopThread >>> 5
    28 # thread LoopThread ended.
    29 # thread MainThread ended.
    实例1

    由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1Thread-2……

     1 import threading
     2 import time
     3 
     4 def countNum(n): # 定义某个线程要运行的函数
     5 
     6     print("running on number:%s" %n)
     7 
     8     time.sleep(3)
     9 
    10 if __name__ == '__main__':
    11 
    12     t1 = threading.Thread(target=countNum,args=(23,)) #生成一个线程实例
    13     t2 = threading.Thread(target=countNum,args=(34,))
    14 
    15     t1.start() #启动线程
    16     t2.start()
    17 
    18     print("ending!")
    19 
    20 
    21 #运行结果:程序打印完“ending!”后等待3秒结束
    22 #running on number:23
    23 #running on number:34
    24 #ending!
    实例2

    该实例中共有3个线程:主线程,t1和t2子线程

    2. 自定义Thread类继承式创建

     1 #继承Thread式创建
     2 
     3 import threading
     4 import time
     5 
     6 class MyThread(threading.Thread):
     7 
     8     def __init__(self,num):
     9         threading.Thread.__init__(self)    #继承父类__init__
    10         self.num=num
    11 
    12     def run(self):    #必须定义run方法
    13         print("running on number:%s" %self.num)
    14         time.sleep(3)
    15 
    16 t1=MyThread(56)
    17 t2=MyThread(78)
    18 
    19 t1.start()
    20 t2.start()
    21 print("ending")
    View Code

    3. Thread类的实例方法

    join和dameon

     1 import threading
     2 from time import ctime,sleep
     3 
     4 def Music(name):
     5 
     6         print ("Begin listening to {name}. {time}".format(name=name,time=ctime()))
     7         sleep(3)
     8         print("end listening {time}".format(time=ctime()))
     9 
    10 def Blog(title):
    11 
    12         print ("Begin recording the {title}. {time}".format(title=title,time=ctime()))
    13         sleep(5)
    14         print('end recording {time}'.format(time=ctime()))
    15 
    16 
    17 threads = []
    18 
    19 
    20 t1 = threading.Thread(target=Music,args=('FILL ME',))
    21 t2 = threading.Thread(target=Blog,args=('',))
    22 
    23 threads.append(t1)
    24 threads.append(t2)
    25 
    26 if __name__ == '__main__':
    27 
    28     #t2.setDaemon(True)
    29 
    30     for t in threads:
    31 
    32         #t.setDaemon(True) #注意:一定在start之前设置
    33         t.start()
    34 
    35         #t.join()
    36 
    37     #t1.join()
    38     #t2.join()    #  考虑这三种join位置下的结果?
    39 
    40     print ("all over %s" %ctime())
    join和setDaemon

    其它方法:

    1 Thread实例对象的方法
    2   # isAlive(): 返回线程是否活动的。
    3   # getName(): 返回线程名。
    4   # setName(): 设置线程名。
    5 
    6 threading模块提供的一些方法:
    7   # threading.currentThread(): 返回当前的线程变量。
    8   # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
    9   # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

    六、GIL

    '''
    
    定义:
    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple 
    native threads from executing Python bytecodes at once. This lock is necessary mainly 
    because CPython’s memory management is not thread-safe. (However, since the GIL 
    exists, other features have grown to depend on the guarantees that it enforces.)
    
    '''

    Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(Global Interpreter Lock)来互斥线程对Python虚拟机的使用。为了支持多线程机制,一个基本的要求就是需要实现不同线程对共享资源访问的互斥,所以引入了GIL。
    GIL:在一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
    在调用任何Python C API之前,要先获得GIL
    GIL缺点:多处理器退化为单处理器;优点:避免大量的加锁解锁操作。

    1. GIL的早期设计

    Python支持多线程,而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,并且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

    2. GIL的影响

    无论你启多少个线程,你有多少个cpu, Python在执行一个进程的时候会淡定的在同一时刻只允许一个线程运行。
    所以,python是无法利用多核CPU实现多线程的。
    这样,python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

    计算密集型实例:

     1 #coding:utf8
     2 from threading import Thread
     3 import time
     4 
     5 def counter():
     6     i = 0
     7     for _ in range(100000000):
     8         i = i + 1
     9     return True
    10 
    11 
    12 def main():
    13     l=[]
    14     start_time = time.time()
    15     for i in range(2):
    16 
    17         t = Thread(target=counter)
    18         t.start()
    19         l.append(t)
    20         t.join()
    21 
    22     for t in l:
    23         t.join()
    24     # counter()
    25     # counter()
    26     end_time = time.time()
    27     print("Total time: {}".format(end_time - start_time))
    28 
    29 if __name__ == '__main__':
    30     main()
    31 
    32 
    33 '''
    34 py2.7:
    35      串行:9.17599987984s
    36      并发:9.26799988747s
    37 py3.6:
    38      串行:9.540389776229858s
    39      并发:9.568442583084106s
    40 
    41 '''
    计算密集型,多线程并发相比串行,没有显著优势

    3. 解决方案

    用multiprocessing替代Thread multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

     1 #coding:utf8
     2 from multiprocessing import Process
     3 import time
     4 
     5 def counter():
     6     i = 0
     7     for _ in range(100000000):
     8         i = i + 1
     9 
    10     return True
    11 
    12 def main():
    13 
    14     l=[]
    15     start_time = time.time()
    16 
    17     # for _ in range(2):
    18     #     t=Process(target=counter)
    19     #     t.start()
    20     #     l.append(t)
    21     #     #t.join()
    22     #
    23     # for t in l:
    24     #    t.join()
    25     counter()
    26     counter()
    27     end_time = time.time()
    28     print("Total time: {}".format(end_time - start_time))
    29 
    30 if __name__ == '__main__':
    31     main()
    32 
    33 
    34 '''
    35 
    36 py2.7:
    37      串行:8.92299985886 s
    38      并行:8.19099998474 s
    39 
    40 py3.6:
    41      串行:9.963459014892578 s
    42      并发:5.1366541385650635 s
    43 
    44 '''
    multiprocess多进程实现并发运算能够提升效率

    当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住,三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。

    小结:因为GIL的存在,只有IO Bound场景下的多线程会得到较好的性能提升;如果对并行计算性能较高的程序可以考虑把核心部分变成C模块,或者索性用其他语言实现;GIL在较长一段时间内将会继续存在,但是会不断对其进行改进。

    七、同步锁(lock)

    多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

     1 import time
     2 import threading
     3 
     4 def subNum():
     5     global num #在每个线程中都获取这个全局变量
     6     temp = num
     7     time.sleep(0.1)
     8     num =temp-1  # 对此公共变量进行-1操作
     9 
    10 num = 100  #设定一个共享变量
    11 thread_list = []
    12 
    13 for i in range(100):
    14     t = threading.Thread(target=subNum)
    15     t.start()
    16     thread_list.append(t)
    17 
    18 for t in thread_list: #等待所有线程执行完毕
    19     t.join()
    20 
    21 print('Result: ', num)
    22 
    23 
    24 #运行结果:
    25 #Result:  99
    多线程共享变量,无法保证变量安全

    如上实例,在一个进程内,设置共享变量num=100,然后创建100个线程,执行num-=1的操作,但是,由于在函数subNum中存在time.sleep(0.1),该语句可以等价于IO操作。于是在这短短的0.1秒的时间内,所有的线程已经创建并启动,拿到了num=100的变量,等待0.1秒过后,最终得到的num其实是99.

    锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

     1 import time
     2 import threading
     3 
     4 def subNum():
     5     global num #在每个线程中都获取这个全局变量
     6     lock.acquire()
     7     temp = num
     8     time.sleep(0.1)
     9     num =temp-1  # 对此公共变量进行-1操作
    10     lock.release()
    11 
    12 
    13 num = 100  #设定一个共享变量
    14 lock = threading.Lock()    #生成一个同步锁对象
    15 thread_list = []
    16 
    17 for i in range(100):
    18     t = threading.Thread(target=subNum)
    19     t.start()
    20     thread_list.append(t)
    21 
    22 for t in thread_list: #等待所有线程执行完毕
    23     t.join()
    24 
    25 print('Result: ', num)
    26 
    27 #运行结果:
    28 #Result:  0
    应用lock方法,保证变量安全

    lock.acquire()与lock.release()包起来的代码段,保证同一时刻只允许一个线程引用。

    1 import threading
    2 
    3 R=threading.Lock()
    4 
    5 R.acquire()
    6 '''
    7 对公共数据的操作
    8 '''
    9 R.release()

    八、死锁与递归锁

    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

     1 import threading
     2 import time
     3 
     4 mutexA = threading.Lock()
     5 mutexB = threading.Lock()
     6 
     7 class MyThread(threading.Thread):
     8 
     9     def __init__(self):
    10         threading.Thread.__init__(self)
    11 
    12     def run(self):
    13         self.fun1()
    14         self.fun2()
    15 
    16     def fun1(self):
    17 
    18         mutexA.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放
    19 
    20         print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    21 
    22         mutexB.acquire()
    23         print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
    24         mutexB.release()
    25 
    26         mutexA.release()
    27 
    28 
    29     def fun2(self):
    30 
    31         mutexB.acquire()
    32         print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
    33         time.sleep(0.2)
    34 
    35         mutexA.acquire()
    36         print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    37         mutexA.release()
    38 
    39         mutexB.release()
    40 
    41 if __name__ == "__main__":
    42 
    43     print("start---------------------------%s"%time.time())
    44 
    45     for i in range(0, 10):
    46         my_thread = MyThread()
    47         my_thread.start()
    48 
    49 
    50 
    51 #运行结果:
    52 #start---------------------------1494316634.4121563
    53 #I am Thread-1 , get res: ResA---1494316634.4121563
    54 #I am Thread-1 , get res: ResB---1494316634.4121563
    55 #I am Thread-1 , get res: ResB---1494316634.4121563
    56 #I am Thread-2 , get res: ResA---1494316634.4121563
    死锁实例

    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

     1 import threading
     2 import time
     3 
     4 # mutexA = threading.Lock()
     5 # mutexB = threading.Lock()
     6 rlock = threading.RLock()
     7 
     8 class MyThread(threading.Thread):
     9 
    10     def __init__(self):
    11         threading.Thread.__init__(self)
    12 
    13     def run(self):
    14         self.fun1()
    15         self.fun2()
    16 
    17     def fun1(self):
    18         rlock.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放
    19 
    20         print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    21 
    22         rlock.acquire()
    23         print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
    24         rlock.release()
    25 
    26         rlock.release()
    27 
    28 
    29     def fun2(self):
    30         rlock.acquire()
    31         print("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
    32         time.sleep(0.2)
    33 
    34         rlock.acquire()
    35         print("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    36         rlock.release()
    37 
    38         rlock.release()
    39 
    40 if __name__ == "__main__":
    41 
    42     print("start---------------------------%s"%time.time())
    43 
    44     for i in range(0, 10):
    45         my_thread = MyThread()
    46         my_thread.start()
    47 
    48 
    49 #运行结果:从以下结果也可以发现,线程之间是竞争关系
    50 """
    51 start---------------------------1494316940.0863945
    52 I am Thread-1 , get res: ResA---1494316940.0873976
    53 I am Thread-1 , get res: ResB---1494316940.0873976
    54 I am Thread-1 , get res: ResB---1494316940.0873976
    55 I am Thread-1 , get res: ResA---1494316940.287911
    56 I am Thread-2 , get res: ResA---1494316940.287911
    57 I am Thread-2 , get res: ResB---1494316940.287911
    58 I am Thread-2 , get res: ResB---1494316940.287911
    59 I am Thread-2 , get res: ResA---1494316940.4883447
    60 I am Thread-4 , get res: ResA---1494316940.4883447
    61 I am Thread-4 , get res: ResB---1494316940.4883447
    62 I am Thread-4 , get res: ResB---1494316940.4883447
    63 I am Thread-4 , get res: ResA---1494316940.6886203
    64 I am Thread-6 , get res: ResA---1494316940.6886203
    65 I am Thread-6 , get res: ResB---1494316940.6896234
    66 I am Thread-6 , get res: ResB---1494316940.6896234
    67 I am Thread-6 , get res: ResA---1494316940.890659
    68 I am Thread-8 , get res: ResA---1494316940.890659
    69 I am Thread-8 , get res: ResB---1494316940.890659
    70 I am Thread-8 , get res: ResB---1494316940.890659
    71 I am Thread-8 , get res: ResA---1494316941.0918815
    72 I am Thread-10 , get res: ResA---1494316941.0918815
    73 I am Thread-10 , get res: ResB---1494316941.0918815
    74 I am Thread-10 , get res: ResB---1494316941.0918815
    75 I am Thread-10 , get res: ResA---1494316941.2923715
    76 I am Thread-5 , get res: ResA---1494316941.2923715
    77 I am Thread-5 , get res: ResB---1494316941.2923715
    78 I am Thread-5 , get res: ResB---1494316941.2923715
    79 I am Thread-5 , get res: ResA---1494316941.493138
    80 I am Thread-9 , get res: ResA---1494316941.493138
    81 I am Thread-9 , get res: ResB---1494316941.493138
    82 I am Thread-9 , get res: ResB---1494316941.493138
    83 I am Thread-9 , get res: ResA---1494316941.6937861
    84 I am Thread-7 , get res: ResA---1494316941.6937861
    85 I am Thread-7 , get res: ResB---1494316941.6937861
    86 I am Thread-7 , get res: ResB---1494316941.6937861
    87 I am Thread-7 , get res: ResA---1494316941.8946414
    88 I am Thread-3 , get res: ResA---1494316941.8946414
    89 I am Thread-3 , get res: ResB---1494316941.8946414
    90 I am Thread-3 , get res: ResB---1494316941.8946414
    91 I am Thread-3 , get res: ResA---1494316942.0956843
    92 """
    递归锁解决死锁

    九、event对象

    线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为False。如果有线程等待一个Event对象, 而这个Event对象的标志为False,那么这个线程将会被一直阻塞直至该标志为True。一个线程如果将一个Event对象的信号标志设置为True,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。

    event.isSet():返回event的状态值;
    
    event.wait():如果 event.isSet()==False将阻塞线程;
    
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    
    event.clear():恢复event的状态值为False。

    可以考虑一种应用场景(仅仅作为说明),例如,我们有多个线程从Redis队列中读取数据来处理,这些线程都要尝试去连接Redis的服务,一般情况下,如果Redis连接不成功,在各个线程的代码中,都会去尝试重新连接。如果我们想要在启动时确保Redis服务正常,才让那些工作线程去连接Redis服务器,那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作:主线程中会去尝试连接Redis服务,如果正常的话,触发事件,各工作线程会尝试连接Redis服务。

     1 import threading
     2 import time
     3 import logging
     4 
     5 logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s',)
     6 
     7 def worker(event):
     8     logging.debug('Waiting for redis ready...')
     9     while not event.isSet():
    10         logging.debug('connect failed...')
    11         event.wait(1)
    12     logging.debug('redis ready, and connect to redis server and do some work [%s]', time.ctime())
    13     time.sleep(1)
    14 
    15 def main():
    16     readis_ready = threading.Event()
    17     t1 = threading.Thread(target=worker, args=(readis_ready,), name='t1')
    18     t1.start()
    19 
    20     t2 = threading.Thread(target=worker, args=(readis_ready,), name='t2')
    21     t2.start()
    22 
    23     logging.debug('first of all, check redis server, make sure it is OK, and then trigger the redis ready event')
    24     time.sleep(3) # simulate the check progress
    25     logging.debug('redis server is running')
    26     readis_ready.set()
    27 
    28 if __name__=="__main__":
    29     main()
    30 
    31 
    32 #运行结果:
    33 (t1        ) Waiting for redis ready...
    34 # (t1        ) connect failed...
    35 # (t2        ) Waiting for redis ready...
    36 # (t2        ) connect failed...
    37 # (MainThread) first of all, check redis server, make sure it is OK, and then trigger the redis ready event
    38 # (t1        ) connect failed...
    39 # (t2        ) connect failed...
    40 # (t2        ) connect failed...
    41 # (t1        ) connect failed...
    42 # (MainThread) redis server is running
    43 # (t2        ) redis ready, and connect to redis server and do some work [Tue May  9 16:15:18 2017]
    44 # (t1        ) redis ready, and connect to redis server and do some work [Tue May  9 16:15:18 2017]
    监听Redis服务

    十、Semaphore(信号量)

    Semaphore管理一个内置的计数器,
    每当调用acquire()时内置计数器-1;
    调用release() 时内置计数器+1;
    计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

    实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

     1 import threading
     2 import time
     3 
     4 semaphore = threading.Semaphore(5)
     5 
     6 def func():
     7     if semaphore.acquire():
     8         print (threading.currentThread().getName() + ' get semaphore')
     9         time.sleep(2)
    10         semaphore.release()
    11 
    12 for i in range(20):
    13   t1 = threading.Thread(target=func)
    14   t1.start()
    15 
    16 
    17 #运行结果:
    18 # Thread-1 get semaphore
    19 # Thread-2 get semaphore
    20 # Thread-3 get semaphore
    21 # Thread-4 get semaphore
    22 # Thread-5 get semaphore
    23 # Thread-6 get semaphore#隔2秒打印
    24 # Thread-7 get semaphore
    25 # Thread-8 get semaphore
    26 # Thread-9 get semaphore
    27 # Thread-10 get semaphore
    28 # Thread-11 get semaphore#隔2秒打印
    29 # Thread-12 get semaphore
    30 # Thread-13 get semaphore
    31 # Thread-14 get semaphore
    32 # Thread-15 get semaphore
    33 # Thread-16 get semaphore#隔2秒打印
    34 # Thread-17 get semaphore
    35 # Thread-18 get semaphore
    36 # Thread-20 get semaphore
    37 # Thread-19 get semaphore
    semaphore实例

    十一、multiprocessing

    Multiprocessing is a package that supports spawning processes using an API similar to the threading module. 
    The multiprocessing package offers both local and remote concurrency,effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. 
    Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows.

    由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

    multiprocessing包是python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

     1 from multiprocessing import Process
     2 import time
     3 def f(name):
     4 
     5     print('hello', name,time.ctime())
     6     time.sleep(1)
     7 
     8 if __name__ == '__main__':
     9     p_list=[]
    10     for i in range(3):
    11         p = Process(target=f, args=('alvin:%s'%i,))
    12         p_list.append(p)
    13         p.start()
    14     for i in p_list:
    15         p.join()
    16     print('end')
    17 
    18 
    19 #运行结果:
    20 #hello alvin:0 Tue May  9 16:41:18 2017
    21 #hello alvin:1 Tue May  9 16:41:18 2017
    22 #hello alvin:2 Tue May  9 16:41:18 2017
    23 #end
    Process类调用
     1 from multiprocessing import Process
     2 import time
     3 
     4 class MyProcess(Process):
     5     def __init__(self):
     6         super(MyProcess, self).__init__()
     7 
     8     def run(self):
     9 
    10         print ('hello', self.name,time.ctime())
    11         time.sleep(1)
    12 
    13 
    14 if __name__ == '__main__':
    15     p_list=[]
    16     for i in range(3):
    17         p = MyProcess()
    18         p.start()
    19         p_list.append(p)
    20 
    21     for p in p_list:
    22         p.join()
    23 
    24     print('end')
    25 
    26 
    27 #运行结果:
    28 #hello MyProcess-1 Tue May  9 16:42:46 2017
    29 #hello MyProcess-2 Tue May  9 16:42:46 2017
    30 #hello MyProcess-3 Tue May  9 16:42:46 2017
    31 #end
    继承Process类调用

    process类:

    构造方法:

    Process([group [, target [, name [, args [, kwargs]]]]])

      group: 线程组,目前还没有实现,库引用中提示必须是None; 
      target: 要执行的方法; 
      name: 进程名; 
      args/kwargs: 要传入方法的参数。

    实例方法:

      is_alive():返回进程是否在运行。

      join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。

      start():进程准备就绪,等待CPU调度

      run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。

      terminate():不管任务是否完成,立即停止工作进程

    属性:

      daemon:和线程的setDeamon功能一样

      name:进程名字。

      pid:进程号。

    实例:

     1 from multiprocessing import Process
     2 import os
     3 import time
     4 def info(name):
     5 
     6 
     7     print("name:",name)
     8     print('parent process:', os.getppid())
     9     print('process id:', os.getpid())
    10     print("------------------")
    11     time.sleep(1)
    12 
    13 def foo(name):
    14 
    15     info(name)
    16 
    17 if __name__ == '__main__':
    18 
    19     info('main process line')
    20 
    21 
    22     p1 = Process(target=info, args=('alvin',))
    23     p2 = Process(target=foo, args=('egon',))
    24     p1.start()
    25     p2.start()
    26 
    27     p1.join()
    28     p2.join()
    29 
    30     print("ending")
    31 
    32 
    33 
    34 #运行结果:
    35 # name: main process line
    36 # parent process: 5112
    37 # process id: 10808
    38 # ------------------
    39 # name: alvin
    40 # name: egon
    41 # parent process: 10808
    42 # process id: 9576
    43 # ------------------
    44 # parent process: 10808
    45 # process id: 9604
    46 # ------------------
    47 # ending
    process类创建多进程

    通过tasklist(Win)或者ps -elf |grep(linux)命令检测每一个进程号(PID)对应的进程名.

    十二、协程

     1 import time
     2 
     3 """
     4 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
     5 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。
     6 """
     7 # 注意到consumer函数是一个generator(生成器):
     8 # 任何包含yield关键字的函数都会自动成为生成器(generator)对象
     9 
    10 def consumer():
    11     r = ''
    12     while True:
    13         # 3、consumer通过yield拿到消息,处理,又通过yield把结果传回;
    14         #    yield指令具有return关键字的作用。然后函数的堆栈会自动冻结(freeze)在这一行。
    15         #    当函数调用者的下一次利用next()或generator.send()或for-in来再次调用该函数时,
    16         #    就会从yield代码的下一行开始,继续执行,再返回下一次迭代结果。通过这种方式,迭代器可以实现无限序列和惰性求值。
    17         n = yield r
    18         if not n:
    19             return
    20         print('[CONSUMER] ←← Consuming %s...' % n)
    21         time.sleep(1)
    22         r = '200 OK'
    23 def produce(c):
    24     # 1、首先调用c.next()启动生成器
    25     next(c)
    26     n = 0
    27     while n < 5:
    28         n = n + 1
    29         print('[PRODUCER] →→ Producing %s...' % n)
    30         # 2、然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
    31         cr = c.send(n)
    32         # 4、produce拿到consumer处理的结果,继续生产下一条消息;
    33         print('[PRODUCER] Consumer return: %s' % cr)
    34     # 5、produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
    35     c.close()
    36 if __name__=='__main__':
    37     # 6、整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
    38     c = consumer()
    39     produce(c)
    40     
    41     
    42 '''
    43 result:
    44 
    45 [PRODUCER] →→ Producing 1...
    46 [CONSUMER] ←← Consuming 1...
    47 [PRODUCER] Consumer return: 200 OK
    48 [PRODUCER] →→ Producing 2...
    49 [CONSUMER] ←← Consuming 2...
    50 [PRODUCER] Consumer return: 200 OK
    51 [PRODUCER] →→ Producing 3...
    52 [CONSUMER] ←← Consuming 3...
    53 [PRODUCER] Consumer return: 200 OK
    54 [PRODUCER] →→ Producing 4...
    55 [CONSUMER] ←← Consuming 4...
    56 [PRODUCER] Consumer return: 200 OK
    57 [PRODUCER] →→ Producing 5...
    58 [CONSUMER] ←← Consuming 5...
    59 [PRODUCER] Consumer return: 200 OK
    60 '''

    greenlet:

    greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库. 

     1 from greenlet import greenlet
     2  
     3 def test1():
     4     print (12)
     5     gr2.switch()
     6     print (34)
     7     gr2.switch()
     8  
     9 def test2():
    10     print (56)
    11     gr1.switch()
    12     print (78)
    13  
    14 gr1 = greenlet(test1)
    15 gr2 = greenlet(test2)
    16 gr1.switch()
    17 
    18 
    19 #运行结果:
    20 #12
    21 #56
    22 #34
    23 #78

    基于greenlet的框架——gevent

    gevent模块实现协程:

    Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。

    gevent是第三方库,通过greenlet实现协程,其基本思想是:

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

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

     1 from gevent import monkey
     2 monkey.patch_all()
     3 import gevent
     4 from urllib import request
     5 import time
     6 
     7 def f(url):
     8     print('GET: %s' % url)
     9     resp = request.urlopen(url)
    10     data = resp.read()
    11     print('%d bytes received from %s.' % (len(data), url))
    12 
    13 start=time.time()
    14 
    15 gevent.joinall([
    16         gevent.spawn(f, 'https://itk.org/'),
    17         gevent.spawn(f, 'https://www.github.com/'),
    18         gevent.spawn(f, 'https://zhihu.com/'),
    19 ])
    20 
    21 print(time.time()-start)
    22 
    23 
    24 
    25 #运行结果:
    26 #GET: https://itk.org/
    27 #GET: https://www.github.com/
    28 #GET: https://zhihu.com/
    29 #9077 bytes received from https://zhihu.com/.
    30 #12323 bytes received from https://itk.org/.
    31 #92574 bytes received from https://www.github.com/.
    32 #3.7679357528686523
    gevent实例
     1 from gevent import monkey
     2 monkey.patch_all()
     3 import gevent
     4 from urllib import request
     5 import time
     6 
     7 def f(url):
     8     print('GET: %s' % url)
     9     resp = request.urlopen(url)
    10     data = resp.read()
    11     print('%d bytes received from %s.' % (len(data), url))
    12 
    13 start=time.time()
    14 
    15 # gevent.joinall([
    16 #         gevent.spawn(f, 'https://itk.org/'),
    17 #         gevent.spawn(f, 'https://www.github.com/'),
    18 #         gevent.spawn(f, 'https://zhihu.com/'),
    19 # ])
    20 
    21 f('https://itk.org/')
    22 f('https://www.github.com/')
    23 f('https://zhihu.com/')
    24 
    25 print(time.time()-start)
    26 
    27 
    28 
    29 #运行结果:
    30 #GET: https://itk.org/
    31 #12323 bytes received from https://itk.org/.
    32 #GET: https://www.github.com/
    33 #92572 bytes received from https://www.github.com/.
    34 #GET: https://zhihu.com/
    35 #8885 bytes received from https://zhihu.com/.
    36 #5.089903354644775
    对比串行方式的运行效率

    参考资料:

    1. http://www.cnblogs.com/yuanchenqi/articles/6755717.html#top

    2. http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192823818768cd506abbc94eb5916192364506fa5d000?t=1494233371173#comments

  • 相关阅读:
    SpringBoot+CXF下Https调用webservice跳过安全证书的配置
    程序员的长安十二时辰:Java实现从Google oauth2.0认证调用谷歌内部api
    springboot集成activiti6.0多数据源的配置
    activiti工作流委托功能的设计和实现
    vue.js带复选框表单的增删改查
    bootstrap-treeview后台Json数据的封装及前台的显示
    你好,Spring!
    超大份线程池,干杯,兄弟!陆
    嗯!这篇多线程不错!伍
    是兄弟!就来看这篇多线程!叁
  • 原文地址:https://www.cnblogs.com/OldJack/p/6826080.html
Copyright © 2011-2022 走看看