python多线程坑_Python多线程多进程那些事儿看这篇就够了~~

自己以前也写过多线程,发现都是零零碎碎,这篇写写详细点,填一下GIL和Python多线程多进程的坑~

总结下GIL的坑和python多线程多进程分别应用场景(IO密集、计算密集)以及具体实现的代码模块。

1545399-20190828070755409-204970012.png

目录

0x01 进程 and 线程 and “GIL”

0x02python多线程&&线程锁&&threading类

0x03 python队列代码实现

0x04 python之线程池实现

0x05 python多进程并行实现

0x01 进程 and 线程 and “GIL”

进程和线程简单举例:

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程.

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

线程与进程的区别:

简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

​ 进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。

​ 多线程可以共享全局变量,多进程不能。多线程中,所有子线程的进程号相同;多进程中,不同的子进程进程号不同。

并行和并发

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

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

同步与异步

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

异步:指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有消息返回时系统会通知进程进行处理,这样可以提高执行效率

所以为了高效率执行就有了并发编程多线程多进程概念,这里就要提一下“GIL”了~

GIL:

首先需要明确的一点是 GIL 并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

那么CPython实现中的GIL又是什么呢?GIL全称 Global Interpreter Lock

使用Python多线程的人都知道,Python中由于GIL(全局解释锁:Global Interpreter Lock)的存在,在多线程时并没有真正的进行多线程计算。

GIL说白了就是伪多线程,一个线程运行其他线程阻塞,使你的多线程代码不是同时执行,而是交替执行。

1545399-20190803100927304-285817569.png

下面用代码来说明GIL的多线程是伪多线程。

单线程执行代码:

from threading importThreadimporttimedefmy_counter():

i=0for _ in range(100000000):

i= i + 1

returnTruedefmain():

thread_array={}

start_time=time.time()for tid in range(2):

t= Thread(target=my_counter)

t.start()

t.join()

end_time=time.time()print("Total time: {}".format(end_time -start_time))if __name__ == '__main__':

main()

两个线程并发执行代码:

from threading importThreadimporttimedefmy_counter():

i=0for _ in range(100000000):

i= i + 1

returnTruedefmain():

thread_array={}

start_time=time.time()for tid in range(2):

t= Thread(target=my_counter)

t.start()

thread_array[tid]=tfor i in range(2):

thread_array[i].join()

end_time=time.time()print("Total time: {}".format(end_time -start_time))if __name__ == '__main__':

main()

结果:

1545399-20190803102046671-1953482586.png

可以看到多线程反而慢了十几秒。。。。。

关于使用多线程反而慢的原因可以参考pcode数量的调度方式~~

我等菜鸟就关心 GIL的存在,是否多线程就废了?当然不是,这里提一下IO密集型和计算密集型

计算密集型

计算密集型,顾名思义就是应用需要非常多的CPU计算资源,在计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。

IO密集型

对于IO密集型的应用,涉及到网络、磁盘IO的任务都是IO密集型任务,大多消耗都是硬盘读写和网络传输的消耗。

那么GIL多线程的不足,其实是对于计算密集型的不足,这个解决可以利用多进程进行解决,而对于IO密集型的任务,我们还是可以使用多多线程进行提升效率。

0x02 python多线程&&线程锁&&threading类

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

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

importtime, threading#新线程执行的代码:

defloop():print 'thread %s is running...' %threading.current_thread().name

n=0while n < 5:

n= n + 1

print 'thread %s >>> %s' %(threading.current_thread().name, n)

time.sleep(1)print 'thread %s ended.' %threading.current_thread().nameprint 'thread %s is running...' %threading.current_thread().name

t= threading.Thread(target=loop, name='LoopThread')

t.start()

t.join()print 'thread %s ended.' % threading.current_thread().name

1545399-20190803104949933-221329832.png

Threading模块的对象

1545399-20190803105844900-304222249.png

Threading模块的Thread类

1545399-20190803105959601-1219812707.png

Thread类方法

1545399-20190803111237451-828931831.png

使用Thread类,可以有多种方法创建线程:

创建Thread类的实例,传递一个函数

创建Thread类的实例,传递一个可调用的类实例

派生Thread类的子类,并创建子类的实例

一般的,我们会采用第一种或者第三种方法。

第一种方法:创建Thread类,传递一个函数

下面的脚本中,我们先实例化Thread类,并传递一个函数(及其参数),当线程执行的时候,函数也会被执行:

importthreadingfrom time importsleep,ctimeimporttime

loops=[1,2,3,4]defloop(name,sleep_time):print('开始循环线程:'+str(name)+'at:'+str(ctime()))

sleep(sleep_time)print('循环'+str(name)+'结束于:'+str(ctime()))defmain():print("程序开始于:"+str(ctime()))

threads=[]

nloops=range(len(loops))for i innloops:

t=threading.Thread(target=loop,args=(i,loops[i])) #循环 实例化4个Thread类,传递函数及其参数,并将线程对象放入一个列表中

threads.append(t)for i innloops:

threads[i].start()#循环 开始线程

for i innloops:

threads[i].join()#循环 join()方法可以让主线程等待所有的线程都执行完毕。

print('任务完成于:'+str(ctime()))if __name__=='__main__':

main()

1545399-20190803114928816-1754043743.png

和thread模块相比,不同点在于:实现同样的效果,thread模块需要锁对象,而threading模块的Thread类不需要。

当所有的线程都分配完成之后,通过调用每个线程的start()方法再让他们开始。相比于thread模块的管理一组锁(分配、获取、释放检查锁状态)来说,threading模块的Thread类只需要为每个线程调用join()方法即可。join(timeout=None)方法将等待线程结束,或者是达到指定的timeout时间时。这种锁又称为自旋锁。

第二种方法:创建Thread类的实例,传递一个可调用的类实例

创建线程时,于传入函数类似的方法是传入一个可调用的类的实例,用于线程执行——这种方法更加接近面向对象的多线程编程。比起一个函数或者从一个函数组中选择而言,这种可调用的类包含一个执行环境,有更好的灵活性。

importthreadingfrom time importsleep,ctime

loops=[1,2,3,4]classThreadFunc(object):def __init__(self,func,args,name=''):

self.name=name

self.func=func

self.args=argsdef __call__(self):

self.func(*self.args)defloop(nloop,nsec):print('开始循环',nloop,'在:'+str(ctime()))

sleep(nsec)print('结束循环',nloop,'于:'+str(ctime()))defmain():print('程序开始于:'+str(ctime()))

threads=[]

nloops=range(len(loops))for i innloops:

t= threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) #传递一个可调用类的实例

threads.append(t)for i innloops:

threads[i].start()#开始所有的线程

for i innloops:

threads[i].join()#等待所有的线程执行完毕

print('任务完成于:'+str(ctime()))if __name__=='__main__':

main()

1545399-20190803130922531-716042203.png

上面主要添加了ThreadFunc类,并在实例化Thread对象时,通过传参的形式同时实例化了可调用类ThreadFunc。这里同时完成了两个实例化。

第三种方法:派生Thread的子类,并创建子类的实例

importthreadingfrom time importsleep,ctime

loops=[1,2,3,4]classMyThread(threading.Thread):def __init__(self,func,args,name=''):

threading.Thread.__init__(self)

self.name=name

self.func=func

self.args=argsdefrun(self):

self.func(*self.args)defloop(nloop,nsec):print('开始循环',nloop,'在:',str(ctime()))

sleep(nsec)print('结束循环',nloop,'于:',str(ctime()))defmain():print('程序开始于:',str(ctime()))

threads=[]

nloops=range(len(loops))for i innloops:

t= MyThread(loop,(i,loops[i]),loop.__name__)

threads.append(t)for i innloops:

threads[i].start()for i innloops:

threads[i].join()print('所有的任务完成于:',str(ctime()))if __name__ =='__main__':

main()

1545399-20190803131542452-1311377583.png

这里继承Threading父类,重写构造函数,重写run函数即可。

threading中还有以下一些属性,简单介绍一下:

Timer类,Timer(int,target=func)  和Thread类类似,只不过它在int秒过后才以target指定的函数开始线程运行

currentThread()  获得当前线程对象

activeCount()  获得当前活动的线程总个数

enumerate()  获得所有活动线程的列表

settrace(func)  设置一跟踪函数,在run执行前执行

setprofile(func)  设置一跟踪函数,在run执行完毕之后执行

这里提一下线程锁:

多线程程序涉及到一个问题,那就是当不同线程要对同一个资源进行修改或利用时会出现混乱,所以有必要引入线程锁。举个例子:

importthreadingfrom time import *

classMyThread(threading.Thread):def __init__(self,counter,name):

threading.Thread.__init__(self)

self.counter=counter

self.name=namedefrun(self):

self.counter[0]+= 1

printself.counter[0]if __name__ == '__main__':

counter=[0]for i in range(1,11):

t=MyThread(counter,i)

t.start()

1545399-20190803135810160-1889524162.png

这里并发了10个线程,在没有混乱的情况下,很明显一个线程的name和经过它处理过后的counter中的数字应该相同。因为没有锁可能引发混乱,想象中,我们可能认为,当某个线程要打印counter中的数字时,别的线程对其作出了改变,从而导致打印出的counter中的数字不符合预期。实际上,这段代码的运行结果很大概率是很整齐的1\n2\n3....10。如果要解释一下,1. 虽然称并发10个线程。但是实际上线程是不可能真的在同一个时间点开始,比如在这个例子中t1启动后,要将循环进入下一轮,创建新的线程对象t2,然后再让t2启动。这段时间虽然很短很短,但是确实是存在的。而这段时间的长度,足够让t1的run中,进行自增并且打印的操作。最终,整个结果看上去似乎没什么毛病。

如果我们想要看到“混乱”的情况,显然两个方法。要么缩短for i in range以及创建线程对象的时间,使得线程在自增之后来不及打印时counter被第二个线程自增,这个比较困难;另一个方法就是延长自增后到打印前的这段时间。自然想到,最简单的,用time.sleep(1)睡一秒即可。此时结果可能是10\n10\n...。主要看第一行的结果。不再是1而是10了。说明在自增操作结束,打印数字之前睡的这一秒里,到第10个线程都成功自增了counter,因此即使是第一个线程,打印到的也是经过第10个线程修改的counter了。

1545399-20190803140013882-31036421.png

线程锁也称互斥锁,可以弥补部分线程安全问题。(线程锁和GIL锁是不一样的东西!)

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

互斥锁有三个常用步骤

lock = threading.Lock() #取得锁

lock.acquire() #上锁

lock.release() #解锁

改一下上面的代码:

importthreadingfrom time import *

classMyThread(threading.Thread):def __init__(self,counter,name,lock):

threading.Thread.__init__(self)

self.counter=counter

self.name=name

self.lock=lockdefrun(self):

self.lock.acquire()

self.counter[0]+= 1sleep(1)printself.counter[0]

self.lock.release()if __name__ == '__main__':

counter=[0]

lock=threading.Lock()for i in range(1,100):

t=MyThread(counter,i,lock)

t.start()

1545399-20190803141747527-728937250.png

锁也可以使用with lock来加锁

with lock:

xxxxxxxxxx

和Lock类类似的还有一个RLock类,与Lock类的区别在于RLock类锁可以嵌套地acquire和release。也就是说在同一个线程中acquire之后再acquire也不会报错,而是将锁的层级加深一层。只有当每一层锁从下到上依次都release开这个锁才算是被解开。RLock锁也称递归锁,

这里还要提一下一个更强大的锁 Condition

上面提到的threading.Lock类提供了最为简单的线程锁的功能。除了Lock和RLock以外,其实threading还补充了其他一些很多的带有锁功能的类。Condition就是其中最为强大的类之一。

acquire(): 线程锁

release(): 释放锁

wait(timeout): 线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。

notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。

notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程

改下上面的代码:

importthreadingfrom time import *

classMyThread(threading.Thread):def __init__(self,counter,name,con):

threading.Thread.__init__(self)

self.counter=counter

self.name=name

self.con=condefrun(self):

self.con.acquire()

self.counter[0]+= 1sleep(1)printself.counter[0]

con.notify()

con.wait()

self.con.release()if __name__ == '__main__':

counter=[0]

con=threading.Condition()for i in range(1,100):

t=MyThread(counter,i,con)

t.start()

1545399-20190803171357240-453957512.png

注意释放锁relase是必要的,不然会出现死锁的现象。

信号量(BoundedSemaphore类)

互斥锁同时只允许一个线程更改数据,而Semaphore信号量是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

importthreadingfrom time import *

classMyThread(threading.Thread):def __init__(self,counter,name):

threading.Thread.__init__(self)

self.counter=counter

self.name=namedefrun(self):

semaphore.acquire()

self.counter[0]+= 1sleep(1)printself.counter[0]

semaphore.release()if __name__ == '__main__':

counter=[0]

semaphore= threading.BoundedSemaphore(5)for i in range(1,100):

t=MyThread(counter,i)

t.start()

事件(Event类)

python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:

方法注释

clear

将flag设置为“False”

set

将flag设置为“True”

is_set

判断是否设置了flag

wait

会一直监听flag,如果没有检测到flag就一直处于阻塞状态

事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。

#利用Event类模拟红绿灯

importthreadingimporttime

event=threading.Event()deflighter():

count=0

event.set()#初始值为绿灯

whileTrue:if 5 < count <=10:

event.clear()#红灯,清除标志位

print("\33[41;1mred light is on...\033[0m")elif count > 10:

event.set()#绿灯,设置标志位

count =0else:print("\33[42;1mgreen light is on...\033[0m")

time.sleep(1)

count+= 1

defcar(name):whileTrue:if event.is_set(): #判断是否设置了标志位

print("[%s] running..."%name)

time.sleep(1)else:print("[%s] sees red light,waiting..."%name)

event.wait()print("[%s] green light is on,start going..."%name)

light= threading.Thread(target=lighter,)

light.start()

car= threading.Thread(target=car,args=("MINI",))

car.start()

定时器(Timer类)

定时器,指定n秒后执行某操作

from threading importTimerdefhello():print("hello, world")

t= Timer(1, hello)

t.start()#after 1 seconds, "hello, world" will be printed

1545399-20190803180357590-450717092.png

0x03 python队列代码实现

Queue队列

Queue用于建立和操作队列,常和threading类一起用来建立一个简单的线程队列。

队列有很多种,根据进出顺序来分类,可以分成

Queue.Queue(maxsize)  FIFO(先进先出队列)

Queue.LifoQueue(maxsize)  LIFO(先进后出队列)

Queue.PriorityQueue(maxsize)  为优先级越高的越先出来,对于一个队列中的所有元素组成的entries,优先队列优先返回的一个元素是sorted(list(entries))[0]。至于对于一般的数据,优先队列取什么东西作为优先度要素进行判断,官方文档给出的建议是一个tuple如(priority, data),取priority作为优先度。

如果设置的maxsize小于1,则表示队列的长度无限长

FIFO是常用的队列,其一些常用的方法有:

Queue.qsize()  返回队列大小

Queue.empty()  判断队列是否为空

Queue.full()  判断队列是否满了

Queue.get([block[,timeout]])  从队列头删除并返回一个item,block默认为True,表示当队列为空却去get的时候会阻塞线程,等待直到有有item出现为止来get出这个item。如果是False的话表明当队列为空你却去get的时候,会引发异常。在block为True的情况下可以再设置timeout参数。表示当队列为空,get阻塞timeout指定的秒数之后还没有get到的话就引发Full异常。

Queue.put(...[,block[,timeout]])  向队尾插入一个item,同样若block=True的话队列满时就阻塞等待有空位出来再put,block=False时引发异常。同get的timeout,put的timeout是在block为True的时候进行超时设置的参数。

Queue.task_done()  从场景上来说,处理完一个get出来的item之后,调用task_done将向队列发出一个信号,表示本任务已经完成

Queue.join()  监视所有item并阻塞主线程,直到所有item都调用了task_done之后主线程才继续向下执行。这么做的好处在于,假如一个线程开始处理最后一个任务,它从任务队列中拿走最后一个任务,此时任务队列就空了但最后那个线程还没处理完。当调用了join之后,主线程就不会因为队列空了而擅自结束,而是等待最后那个线程处理完成了。

结合threading和Queue可以构建出一个简单的生产者-消费者模型,注意,线程队列的意义并不是进一步提高运行效率,而是使线程的并发更加有组织。新线程想要加入队列开始执行,必须等一个既存的线程完成之后才可以。举个例子,比如

from threading importThreadimportqueue, time

q=queue.Queue()defconsumer():while 1:

res=q.get()

time.sleep(2)print('消费者消费了\033[35m%s\033[0m' %res)

q.task_done()defproducer_0():for i in range(5):

q.put(i)print('生产者0生产了\033[35m%s\033[0m' %i)

q.join()defproducer_1():for i in range(5):

q.put(i)print('生产者1生产了\033[32m%s\033[0m' %i)

q.join()defproducer_2():for i in range(5):

q.put(i)print('生产者2生产了\033[33m%s\033[0m' %i)

q.join()if __name__ == '__main__':

t0= Thread(target=producer_0, )

t1= Thread(target=producer_1, )

t2= Thread(target=producer_2, )

t0.start()

t1.start()

t2.start()

consumer_t= Thread(target=consumer, )

consumer_t.daemon=True

consumer_t.start()

t0.join()

t1.join()

t2.join()print('主线程~')

1545399-20190805225644095-409465554.png

0x04 python之线程池实现

线城池

对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控。对于任务数量不端增加的程序,固定线程数量的线程池是必要的。

threadpool模块

threadpool是一个比较老的模块了,支持py2 和 py3 。

importthreadpoolimporttimedefsayhello (a):print("hello:"+a)

time.sleep(2)defmain():globalresult

seed=["a","b","c"]

start=time.time()

task_pool=threadpool.ThreadPool(5)

requests=threadpool.makeRequests(sayhello,seed)for req inrequests:

task_pool.putRequest(req)

task_pool.wait()

end=time.time()

time_m= end-startprint("time:"+str(time_m))

start1=time.time()for each inseed:

sayhello(each)

end1=time.time()print("time1:"+str(end1-start1))if __name__ == '__main__':

main(

1545399-20190805231034636-72781424.png

concurrent.futures模块

from concurrent.futures importThreadPoolExecutorimporttimeimporttimefrom concurrent.futures importThreadPoolExecutor, wait, as_completed

ll=[]defsayhello(a):print("hello:"+a)

ll.append(a)

time.sleep(0.8)defmain():

seed=["a","b","c","e","f","g","h"]

start1=time.time()for each inseed:

sayhello(each)

end1=time.time()print("time1:"+str(end1-start1))

start2=time.time()

with ThreadPoolExecutor(2) as executor:for each inseed:

executor.submit(sayhello,each)

end2=time.time()print("time2:"+str(end2-start2))defmain2():

seed= ["a", "b", "c", "e", "f", "g", "h"]

executor= ThreadPoolExecutor(max_workers=10)

f_list=[]for each inseed:

future=executor.submit(sayhello, each)

f_list.append(future)

wait(f_list)print(ll)print('主线程结束')defmain3():

seed= ["a", "b", "c", "e", "f", "g", "h"]

with ThreadPoolExecutor(max_workers=2) as executor:

f_list=[]for each inseed:

future=executor.submit(sayhello, each)

f_list.append(future)

wait(f_list,return_when='ALL_COMPLETED')print(ll)print('主线程结束')if __name__ == '__main__':

main3()

1545399-20190805231129700-1475099023.png

vthread模块

importvthread

pool_1= vthread.pool(5,gqueue=1) #open a threadpool with 5 threads named 1

pool_2 = vthread.pool(2,gqueue=2) #open a threadpool with 2 threads named 2

@pool_1deffoolfunc1(num):

time.sleep(1)print(f"foolstring1, test3 foolnumb1:{num}")

@pool_2deffoolfunc2(num):

time.sleep(1)print(f"foolstring2, test3 foolnumb2:{num}")

@pool_2deffoolfunc3(num):

time.sleep(1)print(f"foolstring3, test3 foolnumb3:{num}")for i in range(10): foolfunc1(i)for i in range(4): foolfunc2(i)for i in range(2): foolfunc3(i)

1545399-20190805231258660-138178308.png

0x05 python多进程并行实现

前面也说了python多线程的弊端和GIL的内容,适合IO密集型,而如果解决计算密集型时候的多线程呢?那就是多进程。

每个进程的GIL互不影响,多进程来并行编程。

1545399-20190805231515782-1827190578.png

multiprocessing模块

python中多线程无法利用多核优势,如果想要充分地使用多核cpu的资源(os.cpu_count()),在python中大部分情况需要使用多进程,python提供了multiprocessing。

multiprocessing并非是python的一个模块,而是python中多进程管理的一个包

multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。

multiprocessing模块的功能众多:支持子进程,通信和共享数据,执行不同形式的同步,提供了process、Queue、Lock等组件。

需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限与该进程内。

process类

创建进程的类:

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

一些创建process类的参数

roup参数未使用,值始终为None

target表示调用对象,即子进程要执行的任务

args表示调用对象的位置参数元组,args=(1,2,'egon',)

kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}

name为子进程的名称

简单创建进程:

importmultiprocessingdefworker(num):"""thread worker function"""

print('Worker:', num)return

if __name__ == '__main__':

jobs=[]for i in range(5):

p= multiprocessing.Process(target=worker, args=(i,))

jobs.append(p)

p.start()

1545399-20190805232207784-471837840.png

当前进程名:

multiprocessing.current_process().name

守护进程:

mutilprocess.setDaemon(True)

一些常用的函数

p.start():启动进程,并调用该子进程中的p.run()

p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法

p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁

p.is_alive():如果p仍然运行,返回True

p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

一些属性:

p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置

p.name:进程的名称

p.pid:进程的pid

p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

开启子进程例子:

#开进程的方法一:

importtimeimportrandomfrom multiprocessing importProcessdefpiao(name):print('%s piaoing' %name)

time.sleep(random.randrange(1,5))print('%s piao end' %name)

p1=Process(target=piao,args=('egon',)) #必须加,号

p2=Process(target=piao,args=('alex',))

p3=Process(target=piao,args=('wupeqi',))

p4=Process(target=piao,args=('yuanhao',))

p1.start()

p2.start()

p3.start()

p4.start()print('主线程')

join(),当某个进程fork一个子进程后,该进程必须要调用wait等待子进程结束发送的sigchld信号,对子进程进行资源回收等相关工作,否则,子进程会成为僵死进程,被init收养。所以,在multiprocessing.Process实例化一个对象之后,该对象有必要调用join方法,因为在join方法中完成了对底层wait的处理。

from multiprocessing importProcessimporttimeimportrandomclassPiao(Process):def __init__(self,name):

self.name=name

super().__init__()defrun(self):print('%s is piaoing' %self.name)

time.sleep(random.randrange(1,3))print('%s is piao end' %self.name)

p=Piao('egon')

p.start()

p.join(0.0001) #等待p停止,等0.0001秒就不再等了

print('开始')

#join:主进程等,等待子进程结束

#join:主进程等,等待子进程结束

创建守护进程例子:

from multiprocessing importProcessimporttimeimportrandomclassPiao(Process):def __init__(self,name):

self.name=name

super().__init__()defrun(self):print('%s is piaoing' %self.name)

time.sleep(random.randrange(1,3))print('%s is piao end' %self.name)

p=Piao('egon')

p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行

p.start()print('主')

完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当涉及到多线程编程时,确实存在一些点。以下是几个常见的 Python 多线程: 1. 全局解释器锁(GIL):在 Python 中,由于 GIL 的存在,一个线程只能在一个时间点上执行 Python 代码。这意味着即使你有多个线程,你的代码也无法并行执行。因此,如果你想要充分利用多核 CPU,你需要使用多个进程而不是多个线程。 2. 竞争条件:多个线程同时访问共享资源可能会导致竞争条件。例如,如果两个线程同时尝试向同一个文件写入数据,可能会导致数据损坏或丢失。为了避免这种情况,你需要使用锁或其他同步原语来确保每个线程访问资源的顺序。 3. 内存泄漏:多线程应用程序中的内存泄漏可能比单线程程序更难以跟踪和调试。因为多个线程可以同时访问相同的内存地址,所以即使一个线程已经释放了一块内存,其他线程仍然可以访问它。为了避免这种情况,你需要确保在不再需要内存时正确地释放它。 4. 连接池的:当使用连接池时,你需要小心不要在多个线程之间共享连接。如果你的应用程序使用了多个线程,并且每个线程都尝试从连接池中获取连接,则可能会出现竞争条件。为了避免这种情况,你需要为每个线程使用独立的连接池。 总之,在使用 Python 进行多线程编程时,你需要小心处理这些点,并且确保你的应用程序在多线程环境下稳定和可靠。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值