Python--多线程和多进程

python中最重要的并发模型:

  • 多线程
  • 多进程
  • 异步编程

并行是并发概念的一个子集,你可以编写有多个线程或进程的并发程序,但是如果不是在多核处理器上执行这个程序,那么就不能以并行的方式来运行代码。

多线程

什么是多线程

程序员可以将他或她的工作分拆到线程中,这些线程同时运行并共享同一内存上下文。线程之间共享同样的上下文,这意味着你必须保护数据,避免并发访问这些数据。如果两个线程更新相同的没有任何保护的数据,则会发生竞态条件,这被称之为竞争冒险。

Python如何处理多线程

与其他语言不同,Python使用多个内核级线程,每个线程可以运行任何解释器级线程。但是CPython有一些主要的限制,渲染线程在多个上下文中不可用。所有访问Python对象的线程都会被一个全局锁串行化。这是由许多解释器的内部结构完成的,和第三方代码一样,它们不是线程安全的,需要进程保护。这种机制被称为全局解释器锁。尽管有GIL限制,线程在某些情况下确实很有用,如:

  • 构建响应式界面
  • 委派工作
  • 构建多用户应用程序

多线程使用举例

Python中使用多线程有两种方式:实例化和类继承

实例化threading.Thread类创建多线程

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

继承threading.Thread类创建多线程

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.

你可能感兴趣的:(Python)