python中最重要的并发模型:
并行是并发概念的一个子集,你可以编写有多个线程或进程的并发程序,但是如果不是在多核处理器上执行这个程序,那么就不能以并行的方式来运行代码。
程序员可以将他或她的工作分拆到线程中,这些线程同时运行并共享同一内存上下文。线程之间共享同样的上下文,这意味着你必须保护数据,避免并发访问这些数据。如果两个线程更新相同的没有任何保护的数据,则会发生竞态条件,这被称之为竞争冒险。
与其他语言不同,Python使用多个内核级线程,每个线程可以运行任何解释器级线程。但是CPython有一些主要的限制,渲染线程在多个上下文中不可用。所有访问Python对象的线程都会被一个全局锁串行化。这是由许多解释器的内部结构完成的,和第三方代码一样,它们不是线程安全的,需要进程保护。这种机制被称为全局解释器锁。尽管有GIL限制,线程在某些情况下确实很有用,如:
Python中使用多线程有两种方式:实例化和类继承
import sys,random,time
import threading
def loop(count):
n = 0
while n < count:
n = n + 1
output = threading.current_thread().name + ' : ' + str(n)
sys.stdout.write(output + '\n') # 使用sys.stdout.write代替print,是为了避免输出混乱,因为print是非线程安全的。
#print(output)
time.sleep(random.random())
t1 = threading.Thread(target=loop, args=(3,)) # 创建新线程
t2 = threading.Thread(target=loop, args=(4,))
t1.start() # 启动线程
t2.start()
t1.join() # 等待线程结束
t2.join()
输出:
Thread-1 : 1
Thread-2 : 1
Thread-2 : 2
Thread-1 : 2
Thread-2 : 3
Thread-2 : 4
Thread-1 : 3
import sys,random,time
import threading
class myThread(threading.Thread):
def __init__(self, name, counter):
threading.Thread.__init__(self)
self.name = name
self.counter = counter
def run(self):
print('Starting ' + self.name)
n = 0
while n < self.counter:
n = n +1
output = self.name + ': ' + str(n)
sys.stdout.write(output + '\n')
time.sleep(random.random())
t1 = myThread('Thread-1', 3)
t2 = myThread('Thread-2', 4)
t1.start()
t2.start()
如果多个线程对同一个数据进行修改,则会出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire和release方法。
import sys
import threading
ticket_count = 100
threadLock = threading.Lock()
def loop():
global ticket_count
while True:
threadLock.acquire() # 获得锁
if ticket_count > 0:
output = threading.current_thread().name + ' get ticket : ' + str(ticket_count)
sys.stdout.write(output + '\n')
ticket_count = ticket_count - 1
else:
sys.stdout.write('Tickets sold out')
break
threadLock.release() # 释放锁
if __name__ == '__main__':
t1 = threading.Thread(target=loop)
t2 = threading.Thread(target=loop)
t1.start()
t2.start()
Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通函数调用一次返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,父进程可以fork出很多子进程,所以父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID:
import os
def main():
print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
if __name__ == '__main__':
main()
输出:
Process (22597) start...
I (22597) just created a child process (22598).
I am child process (22598) and my parent is 22597.
由于windows没有fork调用,上面的代码在Windows上无法运行
multiprocessing模块就是跨平台的多进程模块,它提供了一个Process类来代表一个进程对象,下面的演示启动一个子进程并等待其结束:
import os
from multiprocessing import Process
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',))
print('Process will start.')
p.start()
p.join()
print('Process end.')
输出:
Parent process 13820.
Process will start.
Run child process test (8024)...
Process end.
如果要启动大量的子进程,可以使用进程池的方式批量创建子进程:
import os, time, random
from multiprocessing import Pool
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 subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
注意:Pool的默认大小是CPU的核数
multiprocessing模块提供了Queue,Pipes等多种方式来交换数据。以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
import os, time, random
from multiprocessing import Process, Queue
def write(q):
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
def read(q):
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
输出:
Put A to queue...
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.