python进程、线程

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

一、基础概念
二、multiprocessing 进程模块
三、queue模块
四、multiprocessing.dummy 线程模块
五、threading 线程模块

------------------------------------------------------------------

 

一、基础概念

进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。一个程序至少有一个进程,一个进程至少有一个线程。多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,多进程程序更健壮。进程是资源分配的最小单位,进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。

线程是共享进程中的数据,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。线程的引入减小了程序并发执行时的开销。线程是程序执行的最小单位(资源调度的最小单位)。

所谓协程,可以简单的理解为多个相互协作的子程序。在同一个线程中,当一个子程序阻塞时,我们可以让程序马上从一个子程序切换到另一个子程序,从而避免CPU因程序阻塞而闲置,这样就可以提升CPU的利用率,相当于用一种协作的方式加速了程序的执行。Python中可以使用多线程和多进程来实现并发,协程也可以实现并发编程。协程实现了协作式并发。线程是系统级别的它们由操作系统调度,而协程则是程序级别的由程序根据需要自己调度。greenlet、gevent(第三方模块)可以实现协程

Python中实现并发编程的三种方案:多线程、多进程、异步I/O。并发编程的好处在于可以提升程序的执行效率以及改善用户体验;坏处在于并发的程序不容易开发和调试,同时对其他程序来说它并不友好。异步任务通常通过多任务协作处理的方式来实现,由于执行时间和顺序的不确定,因此需要通过回调式编程或者 `future`对象来获取任务执行的结果。Python 3通过 `asyncio`模块和 `await`和 `async`关键字(在Python 3.7中正式被列为关键字)来支持异步处理。当程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,`asyncio`就是一种很好的选择。如果程序中有大量的等待与休眠时,也应该考虑 `asyncio`,它很适合编写没有实时数据处理需求的Web应用服务器。

一般多线程要比多进程效率更高,因为进程间的切换需要的资源和开销更大。
在python中计算密集型任务通常使用多进程。
IO密集型任务中,如读写文件,在网络间通信等,适用于多线程。
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,对于线程如何处理好同步与互斥是编写多线程程序的难点。而进程之间的通信需要以通信的方式进行。

同步,异步,并行,并发,串行

同步:多任务开始执行,一个任务A执行结束,才可以执行另一个任务B。只存在一个线程。任务 A、B、C 全部执行完成后才算是结束。

异步:多任务开始执行,只需要主任务 A 执行完成就算结束,主任务执行的时候,可以同时执行异步任务 B、C,主任务 A 可以不需要等待异步任务 B、C 的结果。

并发:多个任务在同一个时间段内同时执行,如果是单核心计算机,CPU 会不断地切换任务来完成并发操作。

并行:多任务在同一个时刻同时执行,计算机需要有多核心,每个核心独立执行一个任务,多个任务同时执行,不需要切换。

串行:是同步的一种实现,就是没有并发,所有任务一个一个执行完成。

并发不一定并行,但并行一定并发

并发和并行其实是异步线程实现的两种形式。并行其实是真正的异步,多核CUP可以同时开启多条线程供多个任务同时执行,互不干扰,如上图的并行,其实和异步图例一样。但是并发就不一样了,是一个伪异步。在单核CUP中只能有一条线程,但是又想执行多个任务。这个时候,只能在一条线程上不停的切换任务,比如任务A执行了20%,任务A停下里,线程让给任务B,任务执行了30%停下,再让任务A执行。这样我们用的时候,由于CUP处理速度快,你看起来好像是同时执行,其实不是的,同一时间只会执行单个任务。

串行是同步的一种实现,就是没有并发,所有任务一个一个执行完成。

并发、并行,是逻辑结构的设计模式。

同步、异步,是逻辑调用方式。

 

 

二、queue模块

queue(队列)是python的标准库,在python2中模块名为Queue,python3中改为了queue。

在python中,多个线程之间的数据是共享的,多个线程进行数据交换的时候不能够保证数据的安全性和一致性,所以当多个线程需要进行数据交换的时候,队列就出现了,队列可以完美解决线程间的数据交换,保证线程间数据的安全性和一致性(简单的来说就是多线程需要加锁,很可能会造成死锁,而queue自带锁。所以多线程结合queue会好的很多)。所以多线程下queue可以随意使用,不会出现写冲突。

multiprocessing中有一个Queue类(multiprocessing.Queue), queue中也有一个Queue类(queue.Queue)。queue.Queue是进程内非阻塞队列。队列是多个进程各自私有的。multiprocess.Queue是跨进程通信队列,队列是是各子进程共有,它支持多进程之间的交互,比如master-worker模式下,master进程写入,work进程消费的模式,支持进程之间的通信。

1、常见队列

"""
queue.Queue(maxsize) 先进先出队列
queue.LifoQueue(maxsize) 后进先出队列
queue.PriorityQueue(maxsize) 优先级队列
queue.deque双线队列
maxsize是个整数,指明了队列中能存放的数据个数的上限。一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。 """
#单向队列 ,先进先出 import queue q = queue.Queue() q.put(123) q.put(456) q.put(789) print(q.get()) # 123 #单向队列,后进先出 import queue q = queue.LifoQueue() q.put(123) q.put(456) q.put(789) print(q.get()) # 789 #优先级队列 ,参数是元组,值越小优先级越高 import queue q = queue.PriorityQueue() q.put((4,123)) q.put((3,456)) q.put((2,789)) q.put((1,111)) q.put((0,666)) print(q.get()) # (0,666) #双向队列 import queue q = queue.deque() q.append(123) q.append(456) q.appendleft(666) print(q.pop()) # 456 print(q.popleft()) # 666

2、Queue 常见方法

import queue
q = queue.Queue(maxsize=0)  # 构造一个先进显出队列,maxsize指定队列长度,为0时,表示队列长度无限制。
q.join() # 等到队列为空时候,在执行别操作 q.qsize() # 返回队列大小 (不可靠) q.empty() # 当队列为空时候,返回True 否则返回False (不可靠) q.full() # 当队列满时候,返回True,否则返回False (不可靠),与 maxsize 大小对应 q.put(item, block=True, timeout=None) # 将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置, #为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置时间,过后, #如果队列无法给出放入item位置,则引发 queue.Full 异常 q.get(block=True, timeout=None) # 移除并返回队列头部一个值,可选参数block默认为True,表示获取值时候,如果队列为空,则阻塞,为False时,不阻塞, #若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置时候,过后,如果队列为空,则引发Empty异常。 q.put_nowait(item) # 等效于 put(item,block=False) q.get_nowait() # 等效于 get(item,block=False)

3、生产者与消费者

import threading
import time
import queue

def producer(i):
    q.put(i)

def consumer():
    print(q.get())
    time.sleep(1)

q = queue.Queue()
for i in range(10):
    t = threading.Thread(target=producer,args=(i,))
    t.start()
for i in range(10):
    t = threading.Thread(target=consumer)
    t.start()

 

 

三、multiprocessing模块

python中的多线程无法利用多核优势,如果想要充分的使用CPU资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。multiprocess中几乎包含了和进程有关的所有子模块。大致分为四个部分:创建进程部分、进程同步部分、进程池部分、进程之间数据共享。multiprocessing常用组件及功能:

管理进程模块:

  • Process(用于创建进程模块)
  • Pool(用于创建管理进程池)
  • Queue(用于进程通信,资源共享)
  • Value,Array(用于进程通信,资源共享)
  • Pipe(用于管道通信)
  • Manager(用于资源共享,Manager模块常与Pool模块一起使用)
Queue、Array只适用Process类申请的多进程共享资源。Manager可以适用Pool和Process类申请的多进程共享资源。

1、运行方式
windows系统下,想要启动一个子进程,必须加上*if __name__=="__main__":*,linux则不需要。

2、父进程中的全局变量能被子进程共享吗?
不行,因为每个进程享有独立的内存数据,如果想要共享资源,可以使用Manage类,或者Queue等模块。

3、子进程中还能再创建子进程吗?
可以,子进程可以再创建进程,线程中也可以创建进程。

 

1、multiprocess.Process模块

Process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

Process([group [, target [, name [, args [, kwargs]]]]])
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args为传给target函数的位置参数,是一个元组形式,必须有逗号,args=(1,2,'egon',) 
4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} 
5 name为子进程的名称
方法:
1 p.start():启动进程,并调用该子进程中的p.run() 2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会
被释放,进而导致死锁
4 p.is_alive():如果p仍然运行,返回True 5 p.join([timeout]):主线程等待p终止。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

属性:
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程。设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性(了解即可)
Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),因此用process()直接创建子进程会无限递归创建。所以必须在创建子进程的部分使用
if __name__
==‘__main__’ 判断保护起来,import 的时候就不会递归运行了。

Process创建子进程

#单独创建子进程 1;
import os
from multiprocessing import Process

def func1(name):
    print('hello', name)
    print(f"我是子进程:{name},我的ID是:{os.getpid()},我的父进程id是:{os.getppid()}")
def func2():
    print('hello')

if __name__ == '__main__': 
    p1 = Process(target=func1, args=('测试',)) # 此处传参必须是元组数据类型 
    p1.start() 

    print(f"我是父进程:{os.getpid()}") 

    p2 = Process(target=func2)
    p2.start()

# 执行结果
''' 
我是父进程:52164
hello
我是子进程:测试,我的ID是:70088,我的父进程id是:52164
'''  


#单独创建子进程 2:
# 通过继承Process类的形式开启进程的方式
import os
from multiprocessing import Process

class MyProcess(Process):
#需要的传参必须写入到__init__方法里面且必须加上super().__init__();因为父类Process里面也有__init__方法。

    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):  #固定名字run !
        print(os.getpid())
        print('%s 正在聊天' % self.name)

if __name__ == '__main__':
    p1 = MyProcess('xiaobai_1')
    p2 = MyProcess('xiaohei_2')
    p1.start()  
    p2.start()
    
#结果
'''
10688
xiaobai_1 正在聊天
72912
xiaohei_2 正在聊天
'''

 

#多进程 1
#默认情况下,创建好的子进程是随机启动的,子进程的执行顺序不是根据自动顺序决定的
import time
from multiprocessing import Process

def func(name):
    print("hello 进程 %d" % name )
    time.sleep(1)

if __name__ == '__main__':
    for i in range(10):
        p = Process(target=func, args=(i,))
        p.start()

#结果
'''
hello 进程 0
hello 进程 5
hello 进程 3
hello 进程 8
hello 进程 1
hello 进程 7
hello 进程 6
hello 进程 2
hello 进程 9
hello 进程 4
'''


#多进程 2
#创建好子进程后将其放入链表中,按顺序启动
import time
from multiprocessing import Process

def func(name):
    print("hello 进程 %d" % name )
    time.sleep(0.1)

if __name__ == '__main__':
    p_lst = []
    for i in range(10):
        p = Process(target=func, args=(i,))
        p_lst.append(p)
        p.start()
        p.join()  # 加上join方法后,父进程就会阻塞等待子进程结束而结束。
    print("父进程执行中")

#结果
'''
hello 进程 0
hello 进程 1
hello 进程 2
hello 进程 3
hello 进程 4
hello 进程 5
hello 进程 6
hello 进程 7
hello 进程 8
hello 进程 9
父进程执行中
'''

2、multiprocessing.Pool模块(进程池)

Pool类描述了一个工作进程池,进程池内部维护一个进程序列,当使用时就去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

如果需要多个子进程时可以考虑使用进程池(pool)来管理。pool创建子进程的方法与Process不同,是通过p.apply_async(func,args=(args))实现,一个池子里能同时运行的任务是取决你电脑的cpu数量,如我的电脑现在是有4个cpu,那会子进程task0,task1,task2,task3可以同时启动,task4则在之前的一个某个进程结束后才开始。

Pool(processes: Optional[int] = ...,initializer: Optional[Callable[..., Any]] = ..., initargs: Iterable[Any] = ..., maxtasksperchild: Optional[int] = ...) 
processes :使用的工作进程的数量,如果processes是None那么使用 os.cpu_count()返回的数量。
initializer: 如果initializer是None,那么每一个工作进程在开始的时候会调用initializer(*initargs)。
maxtasksperchild:工作进程退出之前可以完成的任务数,完成后用一个心的工作进程来替代原进程,来让闲置的资源被释放。maxtasksperchild默认是None,意味着只
要Pool存在工作进程就会一直存活。

方法:
apply(func[, args[, kwds]]) :使用arg和kwds参数调用func函数,结果返回前会一直阻塞,apply_async()更适合并发执行。
apply_async(func[, args[, kwds[, callback[, error_callback]]]]) :会返回一个结果对象。callback可以接收一个参数然后被调用,调用失败时则用error_callback替换callback。
close() : 阻止更多的任务提交到pool,待任务完成后,工作进程会退出。
terminate() : 不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。
join() : wait工作线程的退出,在调用join()前,必须调用close() or terminate()。这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。
map(func, iterable[, chunksize])
map_async(func, iterable[, chunksize[, callback[, error_callback]]])
imap(func, iterable[, chunksize])
imap_unordered(func, iterable[, chunksize])
starmap(func, iterable[, chunksize])
starmap_async(func, iterable[, chunksize[, callback[, error_back]]])

 

#apply同步执行:阻塞式
from  multiprocessing import Pool
import time,os
  
def f1(i):
    time.sleep(0.5)
    print("%s开始执行,进程号为%d"%(i,os.getpid()))
    return i + 100
  
if __name__ == "__main__":
    pool = Pool(2) #开启2个进程
    for i in range(1,10): #会循环调用创建的2个进程来执行任务
        pool.apply(func=f1,args=(i,))

    pool.close()
    pool.join()

'''
2开始执行,进程号为5832
4开始执行,进程号为5832
6开始执行,进程号为5832
8开始执行,进程号为5832
1开始执行,进程号为6848
3开始执行,进程号为6848
5开始执行,进程号为6848
7开始执行,进程号为6848
9开始执行,进程号为6848
'''



#apply_async异步执行:非阻塞式
from multiprocessing import Pool
import os,time,random
 
def worker(msg):
    print("%s开始执行,进程号为%d"%(msg,os.getpid()))
    time.sleep(random.random()*2) # random.random()随机成0-1的浮点数
    print(msg,"执行完毕")
    return 'worker'+str(msg)

def f2(arg):
    print(arg)

if __name__ == '__main__':
    po = Pool(3)  # 最大的进程数为3
    for i in range(0,10):  #每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(worker,(i,),callback=f2)
 
    print("----start----")
    po.close()    # 关闭进程池,关闭后po不再接受新的请求
    po.join()     # 等待po中的所有子进程执行完成,必须放在close语句之后,如果没有添加join(),会导致有的代码没有运行就已经结束了
    print("-----end-----")

'''
----start----
worker2
worker1
worker0
worker4
worker6
worker3
worker5
worker8
worker9
worker7
2开始执行,进程号为3156
2 执行完毕
3开始执行,进程号为3156
3 执行完毕
8开始执行,进程号为3156
8 执行完毕
0开始执行,进程号为716
0 执行完毕
5开始执行,进程号为716
5 执行完毕
9开始执行,进程号为716
9 执行完毕
1开始执行,进程号为6896
1 执行完毕
4开始执行,进程号为6896
4 执行完毕
6开始执行,进程号为6896
6 执行完毕
7开始执行,进程号为6896
7 执行完毕
-----end-----
'''

 

from multiprocessing.pool import Pool

def hhh(i):
    return i * 2

def job1(z):
    return z[0]* z[1]

if __name__ == '__main__':
    pool = Pool(2)
    hh = pool.map(hhh, [i for i in range(16)]) #map()会将第二个参数列表元素一个个的传入第一个参数的函数中
    res = pool.map(job1, [(2, 3), (3, 4)])
    print(hh)
    print(res)


from multiprocessing.pool import Pool
import time

def add_test (i):
    time.sleep(1)
    print(i * i)

if __name__ == "__main__":
    pool = Pool(3)
    pool.map_async(add_test, [i for i in range(16)]) 
    pool.close()
    pool.join()

3、守护进程

主进程创建守护进程,守护进程会随着主进程的结束而结束。被置为守护进程的子进程加了join()(起到阻塞作用),那么主进程会等子进程都运行完。
守护进程是一种在系统后台执行的程序,它独立于控制终端并且执行一些周期任务或触发事件。假如你编写了一个python服务程序,并且在命令行下启动,而你的命令行会话又被终端所控制,python服务成了终端程序的一个子进程。因此如果你关闭了终端,这个命令行程序也会随之关闭。要使你的python服务不受终端影响而常驻系统,就需要将它变成守护进程。

import time
from multiprocessing import Process

def foo():
    print(123)
    time.sleep(3)
    print("end123")

def bar():
    print(456)
    time.sleep(2)
    print("end456")


if __name__ == '__main__':
    p1=Process(target=foo)
    p2=Process(target=bar)

    p1.daemon=True #守护进程
    p1.start()
    # p1.join()

    p2.start()
    p2.join()
   
   #打印该行则主进程代码结束,则守护进程p1应该被终止.可能p1执行的打印信息任务会因为主进程打印(main----)被终止.
    print("-----main-----")

#结果:
#如果foo的用时小于bar,那么foo可以正常完成执行
"""
456
end456
-----main-----
"""

 

 

四、进程间通信/同步

1、为什么进程之间需要通信?

  1. 数据传输。一个进程需要将它的数据发送给另一个进程;
  2. 资源共享。多个进程之间共享同样的资源;
  3. 事件通知。一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件;
  4. 进程控制。有些进程希望完全控制另一个进程的执行(如Debug进程),该控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。

2、进程间通信的几种主要手段:

  1. 管道(Pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
  2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;
  3. 消息队列(Message):消息队列是消息的链接表,有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  6. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

3、Semaphore

信号量,是在进程同步过程中一个比较重要的角色。可以控制临界资源的数量,保证各个进程之间的互斥和同步。可以用来控制对共享资源的访问数量,例如池的最大连接数。

实例:演示一下进程之间利用Semaphore做到同步和互斥,以及控制临界资源数量

复制代码
from multiprocessing import Process, Semaphore, Lock, Queue
import time

buffer = Queue(10)
empty = Semaphore(2)
full = Semaphore(0)
lock = Lock()

class Consumer(Process):

    def run(self):
        global buffer, empty, full, lock
        while True:
            full.acquire()
            lock.acquire()
            buffer.get()
            print('Consumer pop an element')
            time.sleep(1)
            lock.release()
            empty.release()


class Producer(Process):
    def run(self):
        global buffer, empty, full, lock
        while True:
            empty.acquire()
            lock.acquire()
            buffer.put(1)
            print('Producer append an element')
            time.sleep(1)
            lock.release()
            full.release()


if __name__ == '__main__':
    p = Producer()
    c = Consumer()
    p.daemon = c.daemon = True
    p.start()
    c.start()
    p.join()
    c.join()
    print 'Ended!'
复制代码

如上代码实现了注明的生产者和消费者问题,定义了两个进程类,一个是消费者,一个是生产者。

定义了一个共享队列,利用了Queue数据结构,然后定义了两个信号量,一个代表缓冲区空余数,一个表示缓冲区占用数。

生产者Producer使用empty.acquire()方法来占用一个缓冲区位置,然后缓冲区空闲区大小减小1,接下来进行加锁,对缓冲区进行操作。然后释放锁,然后让代表占用的缓冲区位置数量+1,消费者则相反。

运行结果如下:

复制代码
Producer append an element
Producer append an element
Consumer pop an element
Consumer pop an element
Producer append an element
Producer append an element
Consumer pop an element
Consumer pop an element
Producer append an element
Producer append an element
Consumer pop an element
Consumer pop an element
Producer append an element
Producer append an element
复制代码

可以发现两个进程在交替运行,生产者先放入缓冲区物品,然后消费者取出,不停地进行循环。

4、Lock

当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。

具体场景:所有的任务在打印的时候都会向同一个标准输出(stdout)输出。这样输出的字符会混合在一起,无法阅读。使用Lock同步,在一个任务输出完成之后,再允许另一个任务输出,可以避免多个任务同时向终端输出。

# 多进程抢占输出资源
import os
import time
import random
from multiprocessing import Process

def work(n):
    print('%s: %s is running' % (n, os.getpid()))
    time.sleep(random.random())
    print('%s: %s is done' % (n, os.getpid()))

if __name__ == '__main__':
    for i in range(3):
        p = Process(target=work, args=(i,))
        p.start()

# 执行结果
"""
0: 14316 is running
1: 9900 is running
2: 10056 is running
1: 9900 is done
2: 10056 is done
0: 14316 is done
"""
# 使用锁维护执行顺序
import os
import time
from multiprocessing import Process, Lock

def work(lock, n):
    lock.acquire()
    print(f'{n}: {os.getpid()} is running')

    time.sleep(0.5)

    print(f'{n}: {os.getpid()} is done')
    lock.release()

if __name__ == '__main__':
    lock = Lock()  #这个一定要定义为全局
    for i in range(3):
        p = Process(target=work, args=(lock, i))
        p.start()
# 执行结果
"""
0: 15276 is running
0: 15276 is done
1: 6360 is running
1: 6360 is done
2: 14776 is running
2: 14776 is done
"""

上面这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了。加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行修改,牺牲了速度却保证了数据的安全性。

我们一种能够兼顾效率高和帮我们处理好锁问题,这就是mutiprocessing模块为我们提供的基于消息的进程间通信机制:队列和管道。队列和管道都是将数据存放于内存中,队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来。

与多线程不同,多进程之间不会共享全局变量,所以多进程通信需要借助“外力”。这些常用的外力有:Pipe,Queue、Value/Array,Manager。多个进程时,通常使用消息传递来进行进程之间的通信,对于传递消息可以使用Pipe()(用于两个进程之间的连接)或队列Queue(允许多个生产者和消费者)。

我们应该尽可能使用管道和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可扩展性。

5、Pipe通信

Pipe常用来在两个进程间通信,两个进程分别位于管道的两端。管道是可以同时发送和接受消息的。Queue适用于绝大多数场景,为满足普遍性而不得不多方考虑,它因此显得“重”。Pipe更为轻巧,速度更快

multiprocessing.Pipe([duplex])
(con1, con2) = Pipe()
con1管道的一端,负责存储,也可以理解为发送信息
con2管道的另一端,负责读取,也可以理解为接受信息

from multiprocessing import Process, Pipe

def send(pipe):
    pipe.send(['spam'] + [42, 'egg'])   # send 传输一个列表
    pipe.close()

if __name__ == '__main__':
    (con1, con2) = Pipe()                            # 创建两个 Pipe 实例
    sender = Process(target=send, args=(con1, ))     # 函数的参数,args 一定是实例化之后的 Pipe 变量,不能直接写 args=(Pip(),)
    sender.start()                                   # Process 类启动进程
print(f"con2 got: {con2.recv()}") # 管道的另一端 con2 从send收到消息 con2.close()

 

from multiprocessing import Process, Pipe
import time


def consumer(p, name):
    left, right = p
    left.close()
    while True:
        try:
            baozi = right.recv()
            print('%s 收到包子:%s' % (name, baozi))
        except EOFError:
            right.close()
            break


def producer(seq, p):
    left, right = p
    right.close()
    for i in seq:
        left.send(i)
        time.sleep(1)
    else:
        left.close()


if __name__ == '__main__':
    left, right = Pipe()

    c1 = Process(target=consumer, args=((left, right), 'c1'))
    c1.start()

    seq = (i for i in range(10))
    producer(seq, (left, right))

    right.close()
    left.close()

    c1.join()
    print('进程间通信-管道-主进程')

  

管道是可以同时发送和接受消息的:

from multiprocessing import Process, Pipe

def talk(pipe):
    pipe.send(dict(name='Bob', spam=42))            # 传输一个字典
    reply = pipe.recv()                             # 接收传输的数据
    print('talker got:', reply)

if __name__ == '__main__':
    (parentEnd, childEnd) = Pipe()                  # 创建两个 Pipe() 实例,也可以改成 conf1, conf2
    child = Process(target=talk, args=(childEnd,))  # 创建一个 Process 进程,名称为 child
    child.start()                                   # 启动进程
    print('parent got:', parentEnd.recv())          # parentEnd 是一个 Pip() 管道,可以接收 child Process 进程传输的数据
    parentEnd.send({x * 2 for x in 'spam'})         # parentEnd 是一个 Pip() 管道,可以使用 send 方法来传输数据
    child.join()                                    # 传输的数据被 talk 函数内的 pip 管道接收,并赋值给 reply
    print('parent exit')

'''
parent got: {'name': 'Bob', 'spam': 42}
talker got: {'ss', 'mm', 'pp', 'aa'}
parent exit
'''

6、Queue 

1)、Queue 

Queue.put()放数据,默认有block=True和timeout两个参数。当block=True时,写入是阻塞式的,阻塞时间由timeout确定。当队列q被(其他线程)写满后,这段代码就会阻塞,直至其他线程取走数据。Queue.put()方法加上 block=False 的参数,即可解决这个隐蔽的问题。但要注意,非阻塞方式写队列,当队列满时会抛出 exception Queue.Full 的异常
Queue.get():取出队列中目前排在最前面的数据(默认阻塞),Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.full() #判断队列是否满了
Queue.empty() #判断队列是否为空
Queue.qsize() 返回队列的大小 ,不过在 Mac OS 上没法运行。

from multiprocessing import Queue
from multiprocessing import Process

def new_put(q):
    q.put('你好')

def new_get(q):
    ret = q.get()
    print(ret)

if __name__ == '__main__':
    q = Queue()

    p = Process(target=new_put, args=(q, ))
    p.start()
    print(p.name)

    g = Process(target=new_get, args=(q, ))
    g.start()
    print(g.name)

 

from multiprocessing import Process, Queue

def producer(q):  # 生产
    for i in range(1, 6):
        q.put(i)  # 添加一个任务
        print("生产%s馒头" % i)


def consumer(q):  # 消费
    while 1:
        sth = q.get()
        print("消费%s馒头" % sth)


if __name__ == '__main__':
    q = Queue(4)
    p1 = Process(target=producer, args=(q, ))
    p2 = Process(target=producer, args=(q, ))
    p3 = Process(target=producer, args=(q, ))
    c1 = Process(target=consumer, args=(q, ))
    c2 = Process(target=consumer, args=(q, ))

    # 将消费者设置为守护进程,因为消费者里面是死循环
    c1.daemon = True
    c2.daemon = True

    p1.start()
    p2.start()
    p3.start()
    c1.start()
    c2.start()

    p1.join()
    p2.join()
    p3.join()
    print("stop")
queue.Queue适用于线程间通信,但不能用于进程间通信。
multiprocessing.Queue可以用于多进程间通信,但不能用于进程池,想要在进程池中使用队列则要使用multiprocess的Manager类
import time
from multiprocessing import Pool,Manager
 
def producer(queue):
    queue.put("a")
    time.sleep(2)
 
def consumer(queue):
    time.sleep(2)
    data = queue.get()
    print(data)
 
if __name__ == "__main__":
    queue = Manager().Queue()
    pool = Pool() #pool中的进程间通信需要使用Manager
pool.apply_async(producer,args=(queue,)) pool.apply_async(consumer, args=(queue,)) pool.close()

 

2)、JoinableQueue

JoinableQueue比Queue多了task_done和join方法
q.task_done() 使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。也就是put取出了,计数-1。
q.join() 生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。

from multiprocessing import JoinableQueue, Queue

q = JoinableQueue()# 用法和Queue相似
q.put("ocean") # 队列放入一个任务,内存在一个计数机制,+1
print(q.get())
q.task_done() # 完成一次任务,计数机制-1
q.join() # 计数机制不为0的时候,阻塞等待计数器为0后通过

 

from multiprocessing import Process, JoinableQueue


def producer(q):  # 生产
    for i in range(1, 6):
        q.put(i)  # 添加一个任务
        print("生产%s馒头" % i)


def consumer(q):  # 消费
    while 1:
        sth = q.get()
        print("消费%s馒头" % sth)

name__ == '__main__':
    # 继承了Queue,多了两个功能,join() task_done()
    q = JoinableQueue(4)
    p1 = Process(target=producer, args=(q, ))
    p2 = Process(target=producer, args=(q, ))
    p3 = Process(target=producer, args=(q, ))
    c1 = Process(target=consumer, args=(q, ))
    c2 = Process(target=consumer, args=(q, ))

    # 将消费者设置为守护进程,因为消费者里面是死循环
    c1.daemon = True
    c2.daemon = True

    p1.start()
    p2.start()
    p3.start()
    c1.start()
    c2.start()

    p1.join()
    p2.join()
    p3.join()
    print("stop")

 

7、Array,Value

无论是Value()还是Array(),第一个参数都是typecode或type。typecode表示类型码,在Python中已经预先设计好了,如”c“表示char类型,“i”表示singed int类型等等。这种方式不易
记忆,建议用type表达类型,这里需要借助ctypes模块。
# typecode int_typecode
= Value("i", 512) float_typecode = Value("f", 1024.0) char_typecode = Value("c", b"a") # 第二个参数是byte型 # type import ctypes int_type = Value(ctypes.c_int, 512) float_type = Value(ctypes.c_float, 1024.0) char_type = Value(ctypes.c_char, b"a") # 第二个参数是byte型

 

import multiprocessing  
  
def  func(num):  
    num.value=10.78  #子进程改变数值的值,主进程跟着改变  
  
if  __name__=="__main__":  
    num=multiprocessing.Value("d",10.0) # d表示数值,主进程与子进程共享这个value。(主进程与子进程都是用的同一个value)  
    print(num.value)  #10.0
  
    p=multiprocessing.Process(target=func,args=(num,))  
    p.start()  
    p.join()  
  
    print(num.value)  #10.78



import multiprocessing  
  
def  func(num):  
    num[2]=9999   #子进程改变数组,主进程跟着改变  
  
if  __name__=="__main__":  
    num=multiprocessing.Array("i",[1,2,3,4,5])   #主进程与子进程共享这个数组  
    print(num[:])  #[1, 2, 3, 4, 5]
  
    p=multiprocessing.Process(target=func,args=(num,))  
    p.start()   
    p.join()  
  
    print(num[:]) #[1, 2, 9999, 4, 5]



from multiprocessing import Process,Array

def agk(i,arr):
    arr[i]=i+100

if __name__=="__main__":
    target=Array("i",10)
 #target=Array('i',rang(10)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(target[:])    #[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    for i in range(10):
        t=Process(target=agk,args=(i,target))
        t.start()
        t.join()
    print(target[:])   #[100, 101, 102, 103, 104, 105, 106, 107, 108, 109] 主进程可以取到子进程运行的数据




from multiprocessing import Process, Value, Array
import ctypes

def producer(num, string):
    num.value = 1024  #int型
    string[0] = b"z"  # 只能一个一个的赋值,byte型
    string[1] = b"t"
    string[2] = b"y"
 
def consumer(num, string):
    print(num.value)
    print(b"".join(string))
 
if __name__ == "__main__":
    
    num = Value(ctypes.c_int, 512)
    string = Array(ctypes.c_char, 3)  # 设置一个长度为3的数组,字符串需要通过Array实现,而不是Value。
 
    proProcess = Process(target=producer, args=(num, string))
    conProcess = Process(target=consumer, args=(num, string))
    proProcess.start()
    conProcess.start()

8、Manager

Python中进程间共享数据除了基本的pipe、queue、value+array外,还提供了更高层次的封装。Queue和管道Pipe都仅能在进程之间传递数据,不能修改数据,multiprocessing.Manager可以实现在进程之间同时修改一份数据。Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问。从而达到多进程间数据通信且安全。Manager支持的类型有:

dict= Manager().dict() # 字典对象
list=Manager().list() #列表
queue = Manager().Queue() # 队列
lock = Manager().Lock() # 普通锁
rlock = Manager().RLock() # 可冲入锁
cond = Manager().Condition() # 条件锁
semaphore = Manager().Semaphore() # 信号锁
event = Manager().Event() # 事件锁
namespace = Manager().Namespace() # 命名空间

from multiprocessing import Process,Manager

def f(d,n):
    d["name"] = "zhangyanlin"
    d["age"] = 18
    d["Job"] = "pythoner"
    n.reverse()
 
if __name__ == "__main__":

    with Manager() as man:
        d = man.dict()
        n = man.list(range(10))
 
        p = Process(target=f,args=(d,n))
        p.start()
        p.join()
 
        print(d)
        print(n)
#结果
'''
{'name': 'zhangyanlin', 'age': 18, 'Job': 'pythoner'}
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
'''

 

from multiprocessing import Process,Pool,Manager
#通过Queue获取子进程的值,并将他们组成列表
def new_put(q): #放入队列
    q.put('你好')

#j_list=[]
def new_get(q): #从队列中取出
    ret = q.get()
	#在此处加入列表是没用的,每个进程都单独把值加入了列表的拷贝中,主进程中j_list还是[]
	#j_list.append(ret) 
    return ret

j_list=[]
def ru():
    manager = Manager()
    q = manager.Queue()

    for i in range(3):
        p = Process(target=new_put, args=(q, ))
        p.start()
    
    p = Pool(3)
    for j in range(3):
        g = p.apply_async(func=new_get, args=(q, ))
        j_list.append(g.get())#通过get()获取进程函数的返回值并加入列表
    
if __name__ == '__main__':
    ru()
    print(j_list)
'''
['你好', '你好', '你好']
'''


#获取进程函数返回值
from multiprocessing import Pool

def sayHi(num):
    print('hi')
    return num

def mycallback(x):
    print(x)
    list1.append(x)

if __name__ == '__main__':
    
    pool = Pool(4)
    list1 = []
    for i in range(4):
        pool.apply_async(sayHi, (i,), callback=mycallback)
    pool.close()
    pool.join()
    
    print(list1)#[0, 1, 2, 3]



from multiprocessing import Pool

def test(p):     
    return p

if __name__=="__main__":
    pool = Pool(processes=5)
    result=[]
    for i  in range(10):
       pp=pool.apply_async(test, args=(i,))#维持执行的进程总数为10,当一个进程执行完后添加新进程
       result.append(pp)   
    pool.close()
    pool.join()

    result_r=[]
    for i in result:
        result_r.append(i.get())
    print(result_r)  #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]





import multiprocessing
from multiprocessing import Manager

def worker(procnum, returns):
    print(str(procnum) + ' represent!')
    returns.append(procnum)
    return returns

if __name__ == '__main__':
    manager = Manager()
    return_list = manager.list() #也可以使用dict
    jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=worker, args=(i, return_list))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()
    print(return_list)
'''
3 represent!
2 represent!
1 represent!
0 represent!
4 represent!
[2, 3, 1, 0, 4]
'''





from multiprocessing import Process,Manager
import os

def func1(shareList,shareValue,shareDict,lock,i):
    with lock:
        shareValue.value+=1
        shareDict[1]='1'
        shareDict[2]='2'
        shareList[i%5]=i+12
        print("%s开始执行,进程号为%d"%(i,os.getpid()))

if __name__ == '__main__':
    manager=Manager()

    list1=manager.list([1,2,3,4,5])
    dict1=manager.dict()
    array1=manager.Array('i',range(10))
    value1=manager.Value('i',1)
    lock=manager.Lock()

    proc=[]
    for i in range(10): #生成10个进程
        m=Process(target=func1,args=(list1,value1,dict1,lock,i))
        m.start()
        proc.append(m)
    for p in proc:
        p.join()

    print(list1)
    print(dict1)
    print(array1)
    print(value1)
'''
6开始执行,进程号为11532
2开始执行,进程号为7604
4开始执行,进程号为11596
0开始执行,进程号为8392
1开始执行,进程号为16564
3开始执行,进程号为2904
5开始执行,进程号为14280
7开始执行,进程号为12152
8开始执行,进程号为13252
9开始执行,进程号为13120
[17, 13, 19, 20, 21]
{1: '1', 2: '2'}
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Value('i', 11)
'''




from selenium import webdriver
from multiprocessing.dummy import Process, Manager
import time

def worker(i, return_list):
    browser=webdriver.Chrome()
    return_list.append(browser)
    time.sleep(1)
    print(i)
    return_list[i].get('http://selenium-python.readthedocs.io')

if __name__ == '__main__':
    manager = Manager()
    return_list = manager.list() #也可以使用dict
    jobs = []
    for i in range(3):
        p = Process(target=worker, args=(i, return_list))
        jobs.append(p)
        p.start()

    for proc in jobs:
        proc.join()

    print(return_list)

 

 

五、multiprocessing.dummy模块(线程)

multiprocessing.dummy 是multiprocessing的一个子库,可以实现多线程,用法与multiprocessing基本相同。multiprocessing.dummy是对threading的封装。. threading 和 multiprocess.dummy的功能一样,都是多线程。只是multiprocess.dummy的接口与multiprocess的接口相同,使得很方便在多进程与多线程间切换。python的多线程只会使用一个cpu,大多用在处理IO密集型程序中;如果希望处理计算密集型程序,应该使用多进程。

 

getpid打印自身进程号,getppid打印父进程进程号。
multiprocessing.current_process().name 获取进程名
multiprocessing.current_process().ident 获取进程号

threading.currentThread().name 获取线程名
threading.currentThread().ident 获取线程号

multiprocessing.dummy.current_process().name 获取线程名
multiprocessing.dummy.current_process().ident 获取线程号

 

1、子线程无返回值

Multiprocessing.dummy.Pool() 与Multiprocessing.Pool() 的用法一样

非阻塞方法--线程并发执行
multiprocessing.dummy.Pool.apply_async() 和 multiprocessing.dummy.Pool.imap()

阻塞方法---线程顺序执行
multiprocessing.dummy.Pool.apply()和 multiprocessing.dummy.Pool.map()

from multiprocessing.dummy import Pool as Pool
import time

def func(msg):
    print('msg:', msg)
    time.sleep(2)
    print('end:')
    
pool = Pool(processes=3)
for i in range(1, 5):
    msg = 'hello %d' % (i)
    pool.apply_async(func, (msg,))  # 非阻塞
    # pool.apply(func,(msg,))       # 阻塞,apply()源自内建函数,用于间接的调用函数,并且按位置把元祖或字典作为参数传入。
    # pool.imap(func,[msg,])        # 非阻塞, 注意与apply传的参数的区别
    # pool.map(func, [msg, ])       # 阻塞

print('Mark~~~~~~~~~~~~~~~')
pool.close()
pool.join()  # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
print('sub-process done')

2、子线程有返回值

与多进程一样,只有multiprocessing.dummy.Pool.apply_async()可以有返回值,apply,map,imap不可以设置返回值.

 

六、threading模块(线程)

threading 模块通过对 thread 进行二次封装,提供了更方便的 api 来处理线程。

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

1、threading.Thread

Thread(group: None = ..., target: (*args: Any, **kwargs: Any) -> Any | None = ..., name: str | None = ..., args: Iterable = ..., kwargs: 
Mapping[str, Any] | None = ..., *, daemon: bool | None = ...) Thread的参数: * group *应该为None; 当实现ThreadGroup类时保留给以后的扩展。 * target *是要由run()方法调用的可调用对象。 默认为无,表示什么都不会被调用。 * name *是线程名称。 默认情况下,唯一名称的格式为“ Thread-N”,其中N是一个小十进制数字。 * args *是目标调用的参数元组。 默认为()。 * kwargs *是用于目标调用的关键字参数字典。 默认为{}。 如果子类覆盖了该构造函数,则必须确保在对线程执行其他任何操作之前,先调用基类的构造函数(Thread .__ init __())。 Thread的方法: t.start() : 激活线程, t.getName() : 获取线程的名称 t.setName() : 设置线程的名称 t.name : 获取或设置线程的名称 t.is_alive() : 判断线程是否为激活状态 t.isAlive() :判断线程是否为激活状态 t.setDaemon() 设置为后台线程或前台线程(即是否为守护线程,默认为False)必须在执行start()方法之后才可以使用。 t.isDaemon() : 判断是否为守护线程 t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。 t.join() :逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义 t.run() :线程被cpu调度后自动执行线程对象的run方法

 

#线程开启方法1
from threading import Thread  # 创建线程的模块

def task(name):
    print(name)

if __name__ == '__main__':  
    # 开启线程  参数1:方法名(不要带括号)   参数2:参数(元祖)      返回对象
    p = Thread(target=task, args=('线程1',))
    p.start()  # 只是给操作系统发送了一个就绪信号,并不是执行。操作系统接收信号后安排cpu运行

    print('')


#线程开启方法2 - 类的方法
from threading import Thread  # 创建线程的模块

class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):  # 固定名字run !!!必须用固定名
        print(self.name)

if __name__ == '__main__':  # 必须要这样启动 
    p = MyThread('子线程1')
    p.start()
    print('主)

 

七、线程通信/同步

python多线程中提供Lock 、Rlock 、Semaphore 、Event 、Condition 用来保证线程之间的同步。

  • Lock & RLock:互斥锁,用来保证多线程访问共享变量的问题
  • Semaphore对象:Lock互斥锁的加强版,可以被多个线程同时拥有,而Lock只能被某一个线程同时拥有。
  • Event对象:它是线程间通信的方式,相当于信号,一个线程可以给另外一个线程发送信号后让其执行操作。
  • Condition对象:其可以在某些事件触发或者达到特定的条件后才处理数据

 

多个线程竞争一个资源 - 保护临界资源 - 锁(Lock/RLock)
多个线程竞争多个资源(线程数>资源数) - 信号量(Semaphore)
多个线程的调度 - 暂停线程执行/唤醒等待中的线程 - Condition

 

2、threading.RLock和threading.Lock

Lock锁是Python的原始锁,在锁定时不属于任何一个线程。在调用了 lock.acquire() 方法后进入锁定状态,lock.release()方法可以解锁。一个线程上的Lock锁,可以由另外线程解锁。如果是RLock则报错。RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。

RLock被称为重入锁,RLock锁是一个可以被同一个线程多次 acquire 的锁,不论同一个线程调用了多少次的acquire,最后它都必须调用相同次数的 release 才能完全释放锁,这个时候其他的线程才能获取这个锁。重入锁必须由获取它的线程释放,一旦线程获得了重入锁,同一个线程再次获取它将不阻塞。

#未使用锁
import threading
import time

num = 0

def show():
    global num
    num +=1
    time.sleep(1)
    print(f'num={num}')

def dos():
    for i in range(5):
        t = threading.Thread(target=show)  
        t.start()
    print('main thread stop')

if __name__ == '__main__':
    dos()
'''
main thread stop
num=5num=5num=5


num=5
num=5
'''


import threading
import time

num = 0
lock = threading.RLock()

def Func():
    lock.acquire() # 获得锁
    global num
    num += 1
    time.sleep(1)
    print(f'num={num}')
    lock.release() #释放锁

def dos():
    for i in range(5):
        t = threading.Thread(target=Func)  
        t.start()
    print('main thread stop')

if __name__ == '__main__':
    dos()
'''
main thread stop
num=1
num=2
num=3
num=4
num=5
'''

3、threading.Event

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

event = threading.Event()

 

event.clear() # 重置event,将“Flag”置为 False,使得所有该event事件都处于待命状态
event.wait() # 等待接收event的指令,决定是否阻塞程序执行
event.set()# 发送event指令,将“Flag”置为True,使所有设置该event事件的线程执行
event.isSet() #判断标识位是否为Ture。

#当线程执行的时候,如果flag为False,则线程会阻塞,当flag为True的时候,线程不会阻塞。它提供了本地和远程的并发性。
from threading import Event,Thread
 
def dosomthing(event):
    print('start')
    event.wait()
    print('execute')

event_obj = Event()

def des():
    for i in range(5):
        t = Thread(target=dosomthing, args=(event_obj,))
        t.start()

if input('input:') == 'True':
    event_obj.clear() #重置event,使得event.wait()起到阻塞作用
    des()
    event_obj.set()
else:
    event_obj.clear()# 重置event,使得event.wait()起到阻塞作用
    des()

'''
input:True
start
start
start
start
start
execute
execute
execute
execute
execute


input:i
start
start
start
start
start

'''

  

4、threading.Condition

Condition类实现了一个conditon变量,这个变量允许一个或多个线程等待,直到他们被另一个线程通知。

cond = threading.Condition()

cond.acquire() #进入锁定状态
cond.release() #解锁
cond.wait() # 等待指定触发,同时会释放对锁的获取,直到被notify才重新占有琐。
cond.notify() # 发送指定,触发执行

import threading
import time

con = threading.Condition()
num = 0

# 生产者
class Producer(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        global num
        con.acquire() # 锁定线程
        print ("开始添加!")
        while num<=5:
            num += 1
            print(f"锅里有{num}个鱼丸")
            time.sleep(1)
            if num >= 5:
                print ("火锅满了,快吃鱼丸!")
                con.notify()  # 唤醒等待的线程
                con.wait()# 等待通知
        con.release()# 释放锁

# 消费者
class Consumers(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        con.acquire()
        global num
        print ("开始吃啦!")
        for i in range(10):
            num -= 1
            print (f"剩余{num}个鱼丸")
            time.sleep(1)
            if num <= 0:
                print ("锅里没货了,赶紧加鱼丸!")
                con.notify()  # 唤醒其它线程
                con.wait()# 等待通知
        con.release()# 释放锁

p = Producer()
c = Consumers()

p.start()
c.start()

 

5、threading与queue模块

queue.Queue适用于线程间通信,但不能用于进程间通信。
multiprocessing.Queue可以用于多进程间通信,但不能用于进程池,想要在进程池中使用队列则要使用multiprocess的Manager类

from queue import Queue
q = Queue(maxsize=0)

# 默认阻塞程序,等待队列消息,可设置超时时间
q.get(block=True, timeout=None)

# 发送消息:默认会阻塞程序至队列中有空闲位置放入数据
q.put(item, block=True, timeout=None)

# 等待所有的消息都被消费完
q.join()

# 通知队列任务处理已经完成,当所有任务都处理完成时,join() 阻塞将会解除
q.task_done()

# 查询当前队列的消息个数
q.qsize()

# 队列消息是否都被消费完,返回 True/False
q.empty()

# 检测队列里消息是否已满
q.full()

 

import time
import queue

# 下面来通过多线程来处理Queue里面的任务:
def work(q):
    while True:
        if q.empty():
            return
        else:
            t = q.get()
            print(f"当前线程sleep {t} 秒")
            time.sleep(t)

def main():
    q = queue.Queue()
    for i in range(5):
        q.put(i)  # 往队列里生成消息
    # 单线程
    work(q)

if __name__ == "__main__":
    start = time.time()
    main()
    print('耗时:', time.time() - start)
'''
当前线程sleep 0 秒
当前线程sleep 1 秒
当前线程sleep 2 秒
当前线程sleep 3 秒
当前线程sleep 4 秒
耗时: 10.023889064788818
'''



import threading
import time
import queue

# 下面来通过多线程来处理Queue里面的任务:
def work(q):
    while True:
        if q.empty():
            return
        else:
            t = q.get()
            print(f"当前线程sleep {t} 秒")
            time.sleep(t)

def main():
    q = queue.Queue()
    for i in range(5):
        q.put(i)  # 往队列里生成消息

    #多线程
    thread_num = 5
    threads = []

    for i in range(thread_num):
        t = threading.Thread(target=work, args=(q,))
        threads.append(t)
    
    for i in range(thread_num):
        threads[i].start()
    for i in range(thread_num):
        threads[i].join()
 
if __name__ == "__main__":
    start = time.time()
    main()
    print('耗时:', time.time() - start)
'''
当前线程sleep 0 秒
当前线程sleep 1 秒
当前线程sleep 2 秒当前线程sleep 3 秒

当前线程sleep 4 秒
耗时: 4.0168352127075195
'''

6、默认线程、守护线程、阻塞线程

守护线程:子线程会随着主线程的结束而结束,无论子线程是否执行完毕

阻塞线程:主线程会等待子线程的执行结束,才继续执行

#在Python中,默认情况下主线程执行完自己的任务后就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束
import threading 
import time 

def thread(): 
    time.sleep(2) 
    print('---子线程结束---') 

def main(): 
    t1 = threading.Thread(target=thread)
    t1.start() 
    print('---主线程结束---') 

if __name__ =='__main__': 
    main() 

'''
---主线程结束--- 
---子线程结束--- 
'''

#当我们使用setDaemon(True)时,这时子线程为守护线程,主线程一旦执行结束,则全部子线程被强制终止 
import threading 
import time 
def test_thread(): 
    time.sleep(3) 
    print('---子线程结束---') 
def main(): 
    t1 = threading.Thread(target=test_thread,daemon=True)  #设置子线程守护主线程 
    t1.start() 
    print('---主线程结束---') 
if __name__ =='__main__': 
    main() #主线程结束,子线程来不及执行就被强制结束
 
'''
---主线程结束--- 
'''

#线程阻塞,即主线程任务结束以后,进入堵塞状态,一直等待所有的子线程结束以后,主线程再终止
import threading 
import time 

def thread(): 
    time.sleep(5) 
    print('---子线程结束---') 

def main(): 
    t1 = threading.Thread(target=thread)
    t1.start() 
    t1.join()
    print('---主线程结束---') 

if __name__ =='__main__': 
    main() #主线程任务结束以后,进入堵塞状态,一直等待所有的子线程结束以后,主线程再终止 
'''
---子线程结束---
---主线程结束---  
'''

 

在Python中,线程阻塞和线程同步是多线程编程中的两个重要概念,它们之间有着密切的关系但又有所区别。

线程阻塞
线程阻塞指的是线程在执行过程中,因为某些条件未满足而暂停执行,等待条件满足后再继续执行的过程。阻塞通常发生在以下几种情况:

等待资源:如等待I/O操作完成(读写文件、网络通信等)。
等待锁:在尝试获取一个已经被其他线程持有的锁时,线程会被阻塞,直到该锁被释放。
调用sleep()函数:主动让出CPU控制权,等待指定时间后恢复执行。
线程同步
线程同步则是指在多线程环境中,为了防止多个线程同时访问同一份资源造成数据的不一致性或冲突,引入的一种机制来控制线程的执行顺序。常见的线程同步工具包括互斥锁(Lock)、信号量(Semaphore)、条件变量(Condition)等。

关系
线程阻塞往往是线程同步机制的一个结果或者实现手段。例如,当一个线程试图获取一个已被其他线程锁定的资源时,该线程会被阻塞,直到锁被释放,这个过程实际上就是一种线程同步的体现。通过这种阻塞和唤醒机制,可以确保多个线程安全地访问共享资源,避免了诸如竞态条件等问题。

简而言之,线程同步关注的是如何协调多个线程对共享资源的访问,以保证数据的一致性和完整性;而线程阻塞是实现这一目标过程中,线程因等待某种条件而暂停执行的一种状态。二者相辅相成,共同维护多线程程序的正确执行。

 

posted @ 2016-11-15 04:31  liangww  阅读(4334)  评论(0编辑  收藏  举报