Java转Python之并发

来源:互联网 发布:景泰蓝 珐琅 区别 知乎 编辑:程序博客网 时间:2024/06/06 01:04
标题没有使用Java常用的名词“多线程”,是因为Python的并发分为多进程和多线程,进程在multiprocessing模块,线程在threading模块(线程虽然还有_thread模块,但是threading是对_thread的高级封装,使用起来更顺手所以这里只介绍threading

 

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

 

 

先来看Python的多进程代码:

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())    #入参要是元祖,进程内不允许修改    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 Poolimport os, time, randomdef 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, Queueimport 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 threadingimport timelock = threading.Lock()class myThread(threading.Thread):    def __init__(self, threadID, name, counter):        threading.Thread.__init__(self)        self.threadID = threadID        self.name = name        self.counter = counter    def run(self):        # 获得锁,成功获得锁定后返回True        # 可选的timeout参数不填时将一直阻塞直到获得锁定        # 否则超时后将返回False        while self.counter>0 :            lock.acquire()            #打印机打印3次            print_time(self.name, 3)            # 释放锁            lock.release()            self.counter-=1def 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 2017Thread-1: Thu Dec 21 15:34:03 2017Thread-1: Thu Dec 21 15:34:04 2017Thread-1: Thu Dec 21 15:34:05 2017Thread-1: Thu Dec 21 15:34:06 2017Thread-1: Thu Dec 21 15:34:07 2017Thread-2: Thu Dec 21 15:34:08 2017Thread-2: Thu Dec 21 15:34:09 2017Thread-2: Thu Dec 21 15:34:10 2017Thread-1: Thu Dec 21 15:34:12 2017Thread-1: Thu Dec 21 15:34:13 2017Thread-1: Thu Dec 21 15:34:14 2017Thread-2: Thu Dec 21 15:34:15 2017Thread-2: Thu Dec 21 15:34:16 2017Thread-2: Thu Dec 21 15:34:17 2017Thread-2: Thu Dec 21 15:34:18 2017Thread-2: Thu Dec 21 15:34:19 2017Thread-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        self.name = name        self.counter = counter    def run(self):        # 获得锁,成功获得锁定后返回True        # 可选的timeout参数不填时将一直阻塞直到获得锁定        # 否则超时后将返回False        while self.counter>0 :            local.name = self.name            local.count = 3            #打印机打印3次            print_time()            self.counter-=1def 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的方式进程数的限制不再成为瓶颈,为何不用更稳定的方式呢;如果要考虑数据共享先分析通过数据同步方式能否低消耗的解决,能解决还是用多进程,代价很大就用多线程。当然了运算规模很小,需要快餐式消费,还是多线程开销更小。