1、线程是最小的执行单位,而进程中至少一个线程组;如果调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间
Unix/Linux操作系统提供了一个fork()
系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
2、多进程:
在windows平台下,由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing
模块就是跨平台版本的多进程模块
multiprocessing模块提供了一个Process类来代表一个进程对象,下面延时启动一个子进程并等待其结束:
3、python中os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程
# !/user/bin/env python# -*- coding: utf-8 -*-from multiprocessing import Processimport os# 子进程要执行的代码def run_proc(name): print ‘run child process %s (%s)...‘ % (name , os.getpid()) if __name__ == ‘__main__‘: print ‘Parent process %s.‘ % os.getpid() p = Process(target=run_proc,args=(‘test‘,)) # 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动;比创建进程fork()还要简单 print ‘Process will start‘ p.start() p.join() # join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步 print ‘Precess end‘
4、Pool :如果要启用大量的子进程,可以用进程池的方式批量创建子进程
from multiprocessing import Poolimport os,time,random def long_time_task(name): print ‘Run task %s (%s)...‘ % (name,os.getpid()) start = time.time() # 返回当前时间的时间戳 time.sleep(random.random()*3) end = time.time() # 返回当前时间的时间戳 print ‘Task %s runs %0.2f seconds.‘ % (name,(end - start)) if __name__ == ‘__main__‘: print ‘Parent process %s.‘ % os.getpid() p = Pool() for i in range(5): p.apply_async(long_time_task,args=(i,)) print ‘Waiting for all subprocess done...‘ p.close() p.join() print ‘All subprocess done‘
5、进程间的通信
Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据
已Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
from multiprocessing import Process,Queueimport os,time,randomdef write(q): # 写数据进程执行代码 for value in [‘A‘,‘B‘,‘C‘]: print ‘put %s to queue...‘ % value q.put(value) time.sleep(random.random()) # random.random()生成0和1之间的随机符点数def read(q): # 读数据进程执行代码 while True: value = q.get(True) print ‘Get %s from queue.‘ % valueif __name__ == ‘__main__‘: q = Queue() # 父进程创建Queue,并传给各个子进程 pw = Process(target=write,args=(q,)) pr = Process(target=read, args=(q,)) pw.start() # 启动子进程pw,写入 pr.start() # 启动子进程pr,读取 pw.join() # 等待pw结束 pr.terminate() # pr进程里是死循环,无法等待其结束,只能强行终止
进程总结:
在Unix/Linux下,可以使用fork()
调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing
模块。
进程间通信是通过Queue
、Pipes
等实现的。
===============================================================================================
1、线程 : 由于线程是操作系统直接支持的执行单元,因此高级语言都内置多线程的支持,python也不例外,并且python的线程是真正的Posix Thread,而不是模拟出来的线程
Python的标准库中提供了两个模块:thread 和 threading ,thread是低级模块,threading是高级模块对thread进行了封装,绝大多数情况下,我们只需要使用threading这个高级模块
2、启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:
# !/user/bin/env python# -*- coding: utf-8 -*-# 线程import time,threadingdef loop(): print ‘thread %s is runniing...‘ % threading.current_thread().name # 打印当前线程的名称 n = 0 while 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
由于任何线程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,python的threading模块有个current_thread()函数,返回当前线程的实例。主线程实例的名字叫MainThread,
子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅在打印时用来显示用。如果不给子线程起名字,python自动给线程名为Thread-1,Thread-2.....
2、Lock
多线程和多进程最大的不同在于:多进程中,同一个变量各自有一份拷贝存在于每个进程中,互不影响;多进程中,所有变量都由所有线程共享,所以任何一个变量都可以被任何一个线程修改,因此线程之间共享数据最大危险在于多个线程
同时改一个变量,把内容改乱了。此时就用到了Lock
import time,threadingbalance = 0 # 假定是银行存款lock = threading.Lock()def change_it(n): # 该函数实现 现存后取,结果应该为0 global balance balance = balance + n balance = balance - ndef run_thread(n): for i in range(1000): lock.acquire() # 先获取锁,锁定方法acquire() try: change_it(n) # 放心地改 finally: lock.release() # 改完后一定要释放锁t1 = threading.Thread(target=run_thread,args=(5,))t2 = threading.Thread(target=run_thread,args=(8,))t1.start()t2.start()t1.join()t2.join()print balance
运行结果:0 当不加锁时,打印结果可能为负值,银行卡不应有负值;是因为多线程共享一个数据造成;加锁后结果一定为0;存n取出n
分析:当多个线程同时执行lock.acquire()时,只有一个线程能成功获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止
获得锁的线程用完后一定要释放锁,否则哪些苦苦等待锁的线程将永远等待下去,称为死线程。所以用try...finally...来确保锁一定会被释放
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模 式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既 不能执行,
也无法结束,只能靠操作系统强制终止。
------------------------------------------------------------------------
1、ThreadLocal:当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。每个线程独立改变自己的副本,不影响其他线程对应的变量副本
不同于线程同步:解决一个变量同一时刻被多个线程共享的问题(共享一个变量)
ThreadLocal:使得一个变量在多个线程中各自拥有自己的副本(实现资源的隔离)