引入进程和线程的概念及区别
threading模块提供的类:
Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。
1.什么是进程
计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。
进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其它记录其运行轨迹的辅助数据。
操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间,进程也可以通过fork和spawn操作来完成其它的任务。
不过各个进程有自己的内存空间、数据栈等,所以只能使用进程间通讯,而不能直接共享信息。
2.线程的基本概念
线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
3、线程和进程的关系以及区别?
** 进程和线程的关系:**
-
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
-
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
-
(3)处理机分给线程,即真正在处理机上运行的是线程
-
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.
进程与线程的区别:
-
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
-
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
-
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
-
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
1 多进程创建方式
可以归纳为三种:fork,multiprocessing以及进程池Pool。
(1) fork方式
1 import os 2 3 # 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以 4 pid = os.fork() 5 6 if pid == 0: 7 print('哈哈1') 8 else: 9 print('哈哈2')
注意:fork()函数只能在Unix/Linux/Mac上面运行,不可以在Windows上面运行。
说明:
- 程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中
- 然后父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定是0,而父进程中是子进程的 id号
在Unix/Linux操作系统中,提供了一个fork()系统函数,它非常特殊。
普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。
这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。我们可以通过os.getpid()获取当前进程ID,通过os.getppid()获取父进程ID。
那么,父子进程之间的执行有顺序吗?
答案是没有!这完全取决于操作系统的调度算法。
而多次fork()就会产生一个树的结构:
(2)multiprocessing方式
如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?当然可以!由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。
1 import os 2 import time 3 4 from multiprocessing import Process 5 6 def run_proc(name): 7 print('子进程运行中,name%s,pin=%d...'%(name,os.getpid())) 8 9 time.sleep(10) 10 print('子进程已经结束') 11 12 if __name__=='__main__': 13 print('父进程%d.'%os.getpid()) 14 p=Process(target=run_proc,args=('test',)) 15 print('子进程将要执行') 16 p.start()
从结果我们看出,只要通过start()开启了子进程之后,主进程会等待子进程执行完才结束!
Process的语法结构如下:
import os import time from multiprocessing import Process class MyProcess(Process): def __init__(self,name): Process.__init__(self) self.name=name def run(self): print('子进程运行中,name= %s ,pid=%d...' % (self.name, os.getpid())) import time time.sleep(10) print('子进程已结束') if __name__=="__main__": my=MyProcess('test') my.start()
(3)Pool方式
#coding=utf-8 from multiprocessing import Pool import os, time, random def worker(msg): print("%s开始执行,进程号为%d"%(msg, os.getpid())) time.sleep(1) print "%s执行完毕"%(msg) if __name__ == '__main__': po = Pool(3) # 定义一个进程池,最大进程数3 for i in range(10): # Pool.apply_async(要调用的目标,(传递给目标的参数元祖,)) # 每次循环将会用空闲出来的子进程去调用目标 po.apply_async(worker, (i,)) print("----start----") po.close() # 关闭进程池,关闭后po不再接收新的请求 po.join() # 等待po中所有子进程执行完成,必须放在close语句之后 print("-----end-----")
实现一个多进程下的文件夹复制功能:
1 #coding=utf-8 2 3 import os 4 from multiprocessing import Pool 5 6 7 def copyFileTask(name, oldFolderName, newFolderName): 8 # 完成copy一个文件的功能 9 fr = open(oldFolderName+"/"+name, 'rb+') 10 fw = open(newFolderName+"/"+name, 'wb+') 11 12 str = fr.read(1024 * 5) 13 while (str != ''): 14 fw.write(str) 15 str = fr.read(1024 * 5) 16 17 fr.close() 18 fw.close() 19 20 21 def main(): 22 # 获取要copy的文件夹名字 23 oldFolderName = raw_input('请输入文件夹名字:') 24 # 创建一个文件夹 25 newFolderName = oldFolderName+'-复件'.decode('utf-8').encode('gbk') 26 os.mkdir(newFolderName) 27 28 #获取old文件夹里面所有文件的名字 29 fileNames = os.listdir(oldFolderName) 30 31 #使用多进程的方式copy原文件夹所有内容到新的文件夹中 32 pool = Pool(5) 33 for name in fileNames: 34 pool.apply_async(copyFileTask, (name, oldFolderName, newFolderName)) 35 36 pool.close() 37 pool.join() 38 39 if __name__ == '__main__': 40 main()
(4)python进程间通信-Queue
from multiprocessing import Queue,Process import time def write(q): for i in ["A","B","C","D","E"]: print('向队列中添加%s'%i) q.put(i) #print(id(q)) time.sleep(1) def read(q): #print(q.empty()) while not q.empty(): print("从队列中取出来的值是%s"%q.get()) time.sleep(1) if __name__ == '__main__': q=Queue()#创建一个queue队列,当做参数输入子进程中,这样子进程能拿到数据资源 qw=Process(target=write,args=(q,)) qw.start() qw.join(0.1) qr=Process(target=read,args=(q,)) qr.start() qr.join() print("通信完毕")
(1) 在实例化Queue类,可以传递最大消息数,如q = Queue(5),这段代码是指只允许消息队列中最大有5个消息数据。如果不加最大消息数或数量为负值,则表达不限制数量直到占满内存;
(2) Queue.qsize():返回当前队列包含的消息数量;
(3) Queue.empty():如果队列为空,返回True,反之False ;
(4) Queue.full():如果队列满了,返回True,反之False;
(5) Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出”Queue.Empty”异常;
2)如果block值为False,消息列队如果为空,则会立刻抛出”Queue.Empty”异常;
(6) Queue.get_nowait():相当Queue.get(False);
(7) Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;
1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出”Queue.Full”异常;
2)如果block值为False,消息列队如果没有空间可写入,则会立刻抛出”Queue.Full”异常;
(8) Queue.put_nowait(item):相当Queue.put(item, False);
二:进程池(pool)中的Queue
如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会得到一条如下的错误信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.
from multiprocessing import Manager,Pool import time def write(q): for i in ["A","B","C","D","E"]: print("向队列中添加%s"%i) q.put(i) time.sleep(1) def read(q): while not q.empty(): print("从队列中取出的值是%s"%q.get()) time.sleep(1) if __name__ == '__main__': q = Manager().Queue() pool = Pool() pool.apply_async(write,args=(q,)) pool.apply_async(read,args=(q,)) # pool.apply(write,args=(q,)) # pool.apply(read,args=(q,)) pool.close() pool.join() print("数据通信完毕")
(5)管道pipe
pipe()返回两个连接对象代表pipe的两端。每个连接对象都有send()方法和recv()方法。
但是如果两个进程或线程对象同时读取或写入管道两端的数据时,管道中的数据有可能会损坏。
当进程使用的是管道两端的不同的数据则不会有数据损坏的风险。
使用方法:
from multiprocessing import Process,Pipe def f(conn): conn.send('约吗') conn.send('约吗.....') print(conn.recv())#接收数据 conn.close() if __name__=="__main__" : parent_conn,chid_conn=Pipe() p=Process(target=f,args=(chid_conn,)) p.start() print(parent_conn.recv())#接收数据 print(parent_conn.recv()) parent_conn.send('约啊')#发送数据
(6)Manager
进程间的通信Queue()和Pipe(),可以实现进程间的数据传递。但是要使python进程间共享数据,我们就要使用multiprocessing.Manager。
Manager支持list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array。
from multiprocessing import Process, Manager import os def f(d, l): d[os.getpid()] = os.getpid() # 获取进程号 l.append(os.getpid()) print(l) if __name__ == '__main__': with Manager() as manager: d = manager.dict() # 生成一个字典,可以在多个进程间共享和传递 l = manager.list(range(5)) # 生成一个列表,可以在多个进程间共享和传递 p_list = [] for i in range(10): p = Process(target=f, args=(d, l)) p.start() p_list.append(p) for res in p_list: res.join() print(d) print(l)
[0, 1, 2, 3, 4, 16296] [0, 1, 2, 3, 4, 16296, 20360] [0, 1, 2, 3, 4, 16296, 20360, 20772] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936, 18136] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936, 18136, 21184] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936, 18136, 21184, 20552] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936, 18136, 21184, 20552, 21076] [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936, 18136, 21184, 20552, 21076, 21204] {16296: 16296, 20360: 20360, 20772: 20772, 21260: 21260, 20936: 20936, 18136: 18136, 21184: 21184, 20552: 20552, 21076: 21076, 21204: 21204} [0, 1, 2, 3, 4, 16296, 20360, 20772, 21260, 20936, 18136, 21184, 20552, 21076, 21204]
进程间的数据默认不共享,那Manager()如何来同步数据?manager对象控制了一个server进程,这个对象有共享数据和让其他进程能访问数据的代理。一个进程修改了数据后,其实并不会通过manager传送数据,作为代理而言,它不知道数据被改变了。为了在共享数据中同步修改的内容,需要在这些代理的容器中,重新声明这个修改过的内容。
2. Python中的线程
Python的标准库提供了两个模块:thread和threading,thread是低级模块,threading是高级模块,对thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。
from time import ctime,sleep import threading def music(func): print(threading.current_thread()) for i in range(2): print('I was listening to %s. %s'%(func,ctime())) sleep(2) print('end listing %s'%ctime()) t1=threading.Thread(target=music,args=('星晴',))#创建线程,traget执行的函数,args函数参数 t1.start()#开启线程
更多方法:
- start 线程准备就绪,等待CPU调度
- setName 为线程设置名称
- getName 获取线程名称
- setDaemon 设置为后台线程或前台线程(默认);如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
- join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
- run 线程被cpu调度后自动执行线程对象的run方法
- Lock 线程锁(互斥锁Mutex)
- Event
线程交互执行,Join(),Daemon :
1、join ()方法:主线程A中,创建了子线程B,并且在主线程A中调用了B.join(),那么,主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行,那么在调用这个线程时可以使用被调用线程的join方法。
2、setDaemon()方法。主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(),这个的意思是,把主线程A设置为守护线程,这时候,要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出.这就是setDaemon方法的含义,这基本和join是相反的。此外,还有个要特别注意的:必须在start() 方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。
from time import ctime,sleep import threading def music(func): print(threading.current_thread()) for i in range(2): print('I was listening to %s. %s'%(func,ctime())) sleep(2) print('end listing %s'%ctime()) def move(func): print(threading.current_thread()) for i in range(2): print('I was at the %s ! %s'%(func,ctime())) sleep(3) print('end moving %s' % ctime()) threads=[] t1=threading.Thread(target=music,args=('星晴',)) threads.append(t1) t2=threading.Thread(target=move,args=('正义联盟',)) threads.append(t2) #join() if __name__=="__main__": # music(u'七里香') # move(u'功夫') for t in threads: t.start() t.join()#等价于t2.join,目的是为了最后执行最后一条打印信息,join 逐个执行每个线程,执行完毕后继续往下执行, #只有当所有线程执行完毕后才会执行下一个 print('all over %s'%ctime())
from time import ctime,sleep import threading def music(func): #print(threading.current_thread())#线程对象 for i in range(2): print('I was listening to %s. %s'%(func,ctime())) sleep(2) print('end listing %s'%ctime()) def move(func): #print(threading.current_thread())#线程对象 for i in range(2): print('I was at the %s ! %s'%(func,ctime())) sleep(3) print('end moving %s' % ctime()) threads=[] t1=threading.Thread(target=music,args=('星晴',)) threads.append(t1) t2=threading.Thread(target=move,args=('正义联盟',)) threads.append(t2) #Daemon(守护进程)将主线程设置为Daemon线程,它退出时,其它子线程会同时退出,不管是否执行完任务。 if __name__=="__main__": t2.setDaemon(True)#只守护t2,但不守护t1,t1会全部执行完,同时也会引发t2执行,但t2不会执行完,t1执行完,就退出 for t in threads: #t.setDaemon(True)#(执行一次后就退出) t.start() #print(threading.current_thread())#线程对象 #print(threading.active_count())#线程对象的数量(默认会有一条主线程) print('all over %s'%ctime())
线程继承实例化:
import threading import time #t1=threading.Thread(target=music,args=('星晴',)) #继承式调用 class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num=num def run(self):#定义每个线程要运行的函数 print('running on number:%s'%self.num) time.sleep(3) if __name__=='__main__': t1=MyThread(1) t2=MyThread(2) t1.start() t2.start()
3.线程锁(互斥锁Mutex)
1.创建锁:mutex = threading.Lock() 2.锁定:mutex.acquire([timeout]) 3.释放:mutex.release()
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print('--get num:',num ) time.sleep(1) num -=1 #对此公共变量进行-1操作 num = 100 #设定一个共享变量 thread_list = [] for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num )
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁版本:
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print('--get num:',num ) time.sleep(1) lock.acquire() #修改数据前加锁 num -=1 #对此公共变量进行-1操作 lock.release() #修改后释放 num = 100 #设定一个共享变量 thread_list = [] lock = threading.Lock() #生成全局锁 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num )
4.死锁和递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,
它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,
如下就是死锁:
from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('