Python多进程和多线程

多进程

Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

想要在windows平台编写多进程,需要引入multiprocessing模块。

from multiprocessing import Process
import os

def run_proc(name):
    print(‘child pid %s %s /but parent is %s‘ % (name, os.getpid(), os.getppid()))
    #fork出的子进程通过getppid()函数可以拿到父进程id

if __name__ == ‘__main__‘:
    print(‘parent pid is %s‘ % os.getpid())
    p = Process(target=run_proc, args=(‘test‘,)) #multiprocessing模块提供了一个Process类来代表一个进程对象,在这里实例化这个类,传入执行函数及函数参数
    p.start() #启动子进程实例
    p.join() #join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
    print(‘child end‘)

程序结果:

parent pid is 4736
child pid test 5316 /but parent is 4736
child end

join()函数的意思就是,在子进程和主进程做到同时结束

如果注释掉p.join()这一行代码

输出如下:

parent pid is 8152
child end
child pid test 2808 /but parent is 8152

子进程提前结束了,就导致了不同步。

进程池方式可以大量创建子进程

from multiprocessing import Pool
import 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(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print(‘Waiting for all subprocesses done...‘)
    p.close()   #close()函数是指进程池添加完后不允许新的进程加入
    p.join()
    print(‘All subprocesses done.‘)  #Pool(4)代表当前cpu为4核心,所以能同时进行4个子进程 多出的一个task4处于等待

执行结果:

Parent process 5460.
Waiting for all subprocesses done...
Run task 0 (9544)...
Run task 1 (7808)...
Run task 2 (8176)...
Run task 3 (4336)...
Task 3 runs 1.67 seconds.
Run task 4 (4336)...
Task 0 runs 1.95 seconds.
Task 2 runs 1.95 seconds.
Task 4 runs 0.31 seconds.
Task 1 runs 2.52 seconds.
All subprocesses done.

apply_async()异步函数,传入执行函数和函数参数。使每一个子进程不在同一时刻将子进程交给cpu。

进程间的通信

Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。

我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

from multiprocessing import Process, Queue
import os, time, random

#写数据进程执行的代码:
def write(q):
    print(‘Process to write: %s parent pid is : %s‘ % (os.getppid(), os.getpid()))
    for value in [‘A‘, ‘B‘, ‘C‘]:
        print(‘Put %s to queue...‘ % value)
        q.put(value)  #写入队列
        time.sleep(random.random())

# 读数据进程执行的代码:
def read(q):
    print(‘Process to read: %s  parent pid is : %s ‘ % (os.getpid(), os.getppid()))
    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()  # 启动子进程pw,写入:
    pr.start()   # 启动子进程pr,读取:
    pw.join()  # 等待pw结束:
    pr.terminate()  # pr进程里是死循环,无法等待其结束,只能强行终止:

执行结果:

Process to write: 6264 parent pid is : 1740
Put A to queue...
Process to read: 5296 parent pid is : 1740
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

多线程

Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

import threading
import time
import os

# 新线程执行的代码:
def loop():
    print(‘thread %s is running...‘ % threading.current_thread().name)  # threading.current_thread()函数可以拿到当前执行线程的名称
    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().name)

print(‘Main pid is %s‘ % os.getpid())
print(‘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)

执行结果:

Main pid is 8380
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

线程锁LOCk

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

我的理解是,每个进程都会申请属于自己的内存,而线程存在的进程只会申请一次内存、只要存在于同一个进程内的所有线程变量都是共享的。

这个例子是线程共享变量之后带来的问题

import threading

# 假定这是你的银行存款:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

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,8,--8等等;与预期的balance恒为0不同

原因就是t1和t2线程交替执行,变量共享导致了结果的多变性。

从代码看,t1先于t2执行;从结果看,实际上线程的调度由CPU决定的。

解决方法:

解决线程变量共享的关键是,当线程实例创建并调用函数时,给这个函数上锁.t1这个线程使用结束后释放锁,t2再获得锁释放锁.....

import threading

# 假定这是你的银行存款:
balance = 0
lock = threading.Lock()  #创建一个线程锁对象
def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        lock.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)

线程锁并不作用于函数上,还是线程上,因为函数实际上还是由线程实例执行。

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

不管你的CPU多少核心,Python并不能真正的做到多线程并发。

Python中由于使用了全局解释锁(GIL)的原因,代码并不能同时在多核上并发的运行,也就是说,Python的多线程不能并发,很多人会发现使用多线程来改进自己的Python代码后,程序的运行效率却下降了。这篇文章对Python中的全局解释锁(GIL)进行了介绍。作者认为这是Python中最令人头疼的问题。

这个文章写得很清晰:最让人头疼的Python问题

听说go语言的并发很强大...但是语法太难qwer...

时间: 2024-11-08 12:21:10

Python多进程和多线程的相关文章

Python多进程和多线程是鸡肋嘛?【转】

GIL是什么 Python的代码执行由 Python虚拟机(也叫解释器主循环,CPython版本)来控制,Python在设计之初就考虑到在解释器的主循环中,同时只有一个线程在运行.即每个CPU在任意时刻只有一个线程在解释器中运行.对 Python虚拟机访问的控制由全局解释锁GIL控制,正是这个锁来控制同一时刻只有一个线程能够运行.——在单核CPU下的多线程其实都只是并发,不是并行 . 并发与并行区别 并发:两个或多个事件在同一时间间隔发生,或者说交替做不同事件的能力,或者说不同的代码块交替执行.

(转)python多进程、多线程编程

1 概念梳理: 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令. 1.1.2 线程的工作方式 假设你正在读一本书,没有读完,你想休息一下,但是你想在回来时恢复到当时读的具体进度.有一个方法就是记下页数.行数与字数这三个数值,这

第十五章 Python多进程与多线程

15.1 multiprocessing multiprocessing是多进程模块,多进程提供了任务并发性,能充分利用多核处理器.避免了GIL(全局解释锁)对资源的影响. 有以下常用类: 类 描述 Process(group=None, target=None, name=None, args=(), kwargs={}) 派生一个进程对象,然后调用start()方法启动 Pool(processes=None, initializer=None, initargs=()) 返回一个进程池对象

Python 多进程、多线程效率比较

Python 界有条不成文的准则: 计算密集型任务适合多进程,IO 密集型任务适合多线程.本篇来作个比较. 通常来说多线程相对于多进程有优势,因为创建一个进程开销比较大,然而因为在 python 中有 GIL 这把大锁的存在,导致执行计算密集型任务时多线程实际只能是单线程.而且由于线程之间切换的开销导致多线程往往比实际的单线程还要慢,所以在 python 中计算密集型任务通常使用多进程,因为各个进程有各自独立的 GIL,互不干扰. 而在 IO 密集型任务中,CPU 时常处于等待状态,操作系统需要

Python多进程与多线程

1.基本概念 2.多线程内容方法 3.多进程内容方法 1.基本概念 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同的任务.A thread is an execution context, which is all the information a CPU needs to execute a stream of instru

python多进程与多线程使用场景

多进程与多线程区别 多进程使用场景 多线程使用场景

python 多进程与多线程浅析

python多线程是伪多线程,同时间最多只有一个线程在执行,但这样并不代码python的多线程没有作用,对于IO密集型的系统,python的多线程还是能极大的提升性能- 关于python伪多线程可以去了解python GIL的概念. 以下代码涉及python多线程,多进程,进程池相关操作: #encoding:utf-8 from multiprocessing import Pool,Manager,cpu_count,Lock,Process import thread import thr

python多进程和多线程---(自学中,坚持更新)

首先我们知道进程和线程最明显的区别是: 多进程环境中,每个进程有自己的进程数据,各个进程数据之间是相互独立的.一个进程可以有多个线程,多个线程共享该进程的数据. #-*- coding:utf-8 -*- import os from nt import getpid print("current id: %d" %getpid()) 输出 current id: 17452 创建进程: 创建进程,linux下用fork,和linux环境下C编程的fork函数一样,对于父进程而言,返回

python 多进程、多线程

1.多线程: 下面讲一个简单用法,这个模块比较简单,但是实际使用中会遇到很多坑 from multiprocessing import process def go(s): print "主线程 %s " % s if __name__ == "__main__": p = process.Process(target=go, args=(2,)) p.start() 2.多线程: from threading import Thread def go(s): pr