标题没有使用Java常用的名词“多线程”,是因为Python的并发分为多进程和多线程,进程在multiprocessing模块,线程在threading模块(线程虽然还有_thread模块,但是threading是对_thread的高级封装,使用起来更顺手所以这里只介绍threading)
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
先来看Python的多进程代码:
from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name) :
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
#入参要是元祖,进程内不允许修改
p1 = Process(target=run_proc, args=('test-1',))
p2 = Process(target=run_proc, args=('test-2',))
print('Child process will start.')
p1.start()
p2.start()
p1.join()
p2.join()
print('Child process end.')运行结果:
Parent process 4956.
Child process will start.
Run child process test-1 (11108)...
Run child process test-2 (11172)...
Child process end.共有3个进程,主进程和2个子进程,参要是元祖,进程内不允许修改,Java多线程中也会将入参设置成final类型。还可以用进程池来管理:
from multiprocessing import Pool
import os, time, random
def myTask(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random())
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(2)
#进程数故意比池多
for i in range(5):
p.apply_async(myTask, args=(i,))
print('Waiting for all subprocesses done...')
#join之前必须closs,禁止进程池再接收新任务
p.close()
#等待所有进程执行完毕
p.join()
print('All subprocesses done.')
运行结果:
Parent process 15160.
Waiting for all subprocesses done...
Run task 0 (12588)...
Run task 1 (7948)...
Task 1 runs 0.37 seconds.
Run task 2 (7948)...
Task 0 runs 0.73 seconds.
Run task 3 (12588)...
Task 2 runs 0.48 seconds.
Run task 4 (7948)...
Task 3 runs 0.58 seconds.
Task 4 runs 0.54 seconds.
All subprocesses done.进程池的方式可以控制运行中的进程数量,通过观察运行结果中进程号看得出进程池中只有两个进程。
进程间如何通信?使用Queue!现在有3个进程,2个负责写1个负责读:
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for element in ['A', 'B', 'C','D','E','F','G','H']:
print('Write %s' %element)
q.put(element)
time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
while True:
#queue是阻塞队列,省去很多麻烦
element = q.get(True)
print('Read %s' %element)
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw1 = Process(target=write, args=(q,))
pw2 = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw1.start()
pw2.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw1.join()
pw2.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
看完多进程我们再来看python多线程的开发,回到了Java人员熟悉的多线程算是如鱼得水,编码思路跟多进程基本一样,不同点是考虑到线程安全问题需要引入锁和ThreadLocal的概念。
Python锁的两个核心方法是:加锁lock.acquire()和释放锁lock.release(),为了保证锁一定要被释放我们往往将release()放在finally里
例子:
import threading
import time
lock = threading.Lock()
class myThread(threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
= name
self.counter = counter
def run(self):
# 获得锁,成功获得锁定后返回True
# 可选的timeout参数不填时将一直阻塞直到获得锁定
# 否则超时后将返回False
while self.counter>0 :
lock.acquire()
#打印机打印3次
print_time(, 3)
# 释放锁
lock.release()
self.counter-=1
def print_time(threadName, counter):
while counter:
time.sleep(1)
print("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 3)
thread2 = myThread(2, "Thread-2", 3)
# 开启新线程
thread1.start()
thread2.start()
#等待线程执行完毕
thread1.join()
thread2.join()执行结果:
Thread-1: Thu Dec 21 15:34:02 2017
Thread-1: Thu Dec 21 15:34:03 2017
Thread-1: Thu Dec 21 15:34:04 2017
Thread-1: Thu Dec 21 15:34:05 2017
Thread-1: Thu Dec 21 15:34:06 2017
Thread-1: Thu Dec 21 15:34:07 2017
Thread-2: Thu Dec 21 15:34:08 2017
Thread-2: Thu Dec 21 15:34:09 2017
Thread-2: Thu Dec 21 15:34:10 2017
Thread-1: Thu Dec 21 15:34:12 2017
Thread-1: Thu Dec 21 15:34:13 2017
Thread-1: Thu Dec 21 15:34:14 2017
Thread-2: Thu Dec 21 15:34:15 2017
Thread-2: Thu Dec 21 15:34:16 2017
Thread-2: Thu Dec 21 15:34:17 2017
Thread-2: Thu Dec 21 15:34:18 2017
Thread-2: Thu Dec 21 15:34:19 2017
Thread-2: Thu Dec 21 15:34:20 2017
锁虽然能解决线程间资源共享的问题,但是必然会带来竞争,而且处理不好容易产生死锁。
为了线程安全 Python 也有 Threadlocal 的解决思路,跟前面介绍的一期 Java 中原理一样<ThreadLocal-单例模式下高并发线程安全>。
代码:
import threading,time,random
# 创建全局ThreadLocal对象:
local = threading.local()
class myThread(threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
= name
self.counter = counter
def run(self):
# 获得锁,成功获得锁定后返回True
# 可选的timeout参数不填时将一直阻塞直到获得锁定
# 否则超时后将返回False
while self.counter>0 :
local.name =
local.count = 3
#打印机打印3次
print_time()
self.counter-=1
def print_time():
threadName = local.name
counter = local.count
while counter:
time.sleep(random.random())
print("%s: %s " % (threadName, time.ctime(time.time())))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 3)
thread2 = myThread(2, "Thread-2", 3)
# 开启新线程
thread1.start()
thread2.start()
#等待线程执行完毕
thread1.join()
thread2.join()
那么问题来了,多进程好还是多线程好?
多进程和多线程的任务调度都是Master-Worker模式的,Workder真正负责运算,Master负责监督Workder和收集运算结果。在多进程中,主进程是Master,子进程是Worker;在多线程中主线程是Master,子线程是Worker。
稳定性方面多进程比多线程要好,因为进程是独立分开的,子进程出现问题不会影响到其它进程;而多线程是共享进程内存的,子线程出现问题可能会导致该进程内整所有子线程一起完蛋!
在开销上多进程要比多线程要大,多线程要轻量很多,但是无论是多线程还是多进程同时运行的数量都不能太多,CPU调度会出现竞争,用于任务切换的消耗将远远大于用于worker任务的消耗。
数据共享方面,进程使用各自独立的存储,数据只能同步和交互无法共享;多线程间共用进程的同一个存储,可以共享同一个资源对象(也就是说非线程安全带来的好处)。
对于Java语言我们没得选,并发只能用多线程。但是对于Python语言当我们有的选的时候,先看规模,规模都很大的情况下如果不需要考虑数据共享,尽量用多进程,因为在分布式、微服务流行的年代,通过添加PC的方式进程数的限制不再成为瓶颈,为何不用更稳定的方式呢;如果要考虑数据共享先分析通过数据同步方式能否低消耗的解决,能解决还是用多进程,代价很大就用多线程。当然了运算规模很小,需要快餐式消费,还是多线程开销更小。
















