python多进程和多线程看这一篇就够了

2023-10-27

脑海中关于进程和线程的概念一直很模糊,什么时候该用多进程,什么时候该用多线程总是搞不清楚。同时python因为历史遗留问题存在GIL全局锁,就让人更加困惑。这一篇就完整整理一下python中进程和线程的概念和实现。

进程和线程

进程(process)和线程(thread)的区别应该算是个老生常谈的话题。

这里引用知乎用户的一个高赞回答来深入浅出的解释一下

看了一遍排在前面的答案,类似”进程是资源分配的最小单位,线程是CPU调度的最小单位“这样的回答感觉太抽象,都不太容易让人理解。

做个简单的比喻:进程=火车,线程=车厢线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

作者:知乎用户
链接:https://www.zhihu.com/question/25532384/answer/411179772
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这里有几个知识点要重点记录一下

  • 单个CPU在任一时刻只能执行单个线程,只有多核CPU还能真正做到多个线程同时运行
  • 一个进程包含多个线程,这些线程可以分布在多个CPU上
  • 多核CPU同时运行的线程可以属于单个进程或不同进程
  • 所以,在大多数编程语言中因为切换消耗的资源更少,多线程比多进程效率更高

坏消息,Python是个特例!

GIL锁

python始于1991年,创立初期对运算的要求不高,为了解决多线程共享内存的数据安全问题,引入了GIL锁,全称为Global Interpreter Lock,也就是全局解释器锁。

GIL规定,在一个进程中每次只能有一个线程在运行。这个GIL锁相当于是线程运行的资格证,某个线程想要运行,首先要获得GIL锁,然后遇到IO或者超时的时候释放GIL锁,给其余的线程去竞争,竞争成功的线程获得GIL锁得到下一次运行的机会。

正是因为有GIL的存在,python的多线程其实是假的,所以才有人说python的多线程非常鸡肋。但是虽然每个进程有一个GIL锁,进程和进程之前还是不受影响的。

GIL是个历史遗留问题,过去的版本迭代都是以GIL为基础来的,想要去除GIL还真不是一件容易的事,所以我们要做好和GIL长期面对的准备。

多进程 vs 多线程

那么是不是意味着python中就只能使用多进程去提高效率,多线程就要被淘汰了呢?

那也不是的。

这里分两种情况来讨论,CPU密集型操作IO密集型操作。针对前者,大多数时间花在CPU运算上,所以希望CPU利用的越充分越好,这时候使用多进程是合适的,同时运行的进程数和CPU的核数相同;针对后者,大多数时间花在IO交互的等待上,此时一个CPU和多个CPU是没有太大差别的,反而是线程切换比进程切换要轻量得多,这时候使用多线程是合适的。

所以有了结论:

  • CPU密集型操作使用多进程比较合适,例如海量运算
  • IO密集型操作使用多线程比较合适,例如爬虫,文件处理,批量ssh操作服务器等等

代码实现

下面来详细看看多进程和多线程是如何实现的。

创建一个函数用来执行

def func():
    print('process {} starts'.format(os.getpid()))
    time.sleep(2)
    print('process {} ends'.format(os.getpid()))

为了表示一个耗时任务,这个函数会休眠2秒钟,并在开始和结尾处打印该函数执行时候的进程ID。

多进程

做为对比,首先来看看顺序执行两边函数的情况

if __name__ == '__main__':
    print('main process is {}'.format(os.getpid()))
    start_time = time.time()
    ### single process
    func()
    func()
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

打印结果如下

main process is 24308
process 24308 starts
process 24308 ends
process 24308 starts
process 24308 ends
total time is 4.001222372055054

可以看到,这里是单个进程先后顺序执行了两遍函数,共耗时约4秒。

下面来看看多进程的情况

if __name__ == '__main__':
    print('main process is {}'.format(os.getpid()))
    start_time = time.time()
    ### multiprocess
    from multiprocessing import Process
    p1 = Process(target=func)
    p2 = Process(target=func)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

从主进程创建新的进程使用的是Process类,该类在实例化时通常接两个参数

  • target - 新的进程执行的函数的函数名
  • args - 函数的参数,元组格式传入

这里因为func函数没有参数需要传递,所以args没有赋值。

创建完Process对象以后通过start()方法来启动该进程,同时如果想让某个进程阻塞主进程,可以执行该进程的join()方法。正常情况下创建完子进程以后主进程会继续向下执行直到结束,如果有子进程阻塞了主进程则主进程会等待该子进程执行完以后才向下执行。这里主进程会等待p1和p2两个子进程都执行完毕才计算结束时间。

打印结果如下

main process is 33536
process 25764 starts
process 11960 starts
process 25764 ends
process 11960 ends
total time is 2.3870742321014404

可以看到,创建的子进程和主进程的进程ID是不一样的,说明此时一共有三个进程在同时跑。最后的用时为2.387秒,几乎降到了顺序执行一半的程度,当然比单个函数执行的时间还是慢了点,说明进程的创建和停止还是需要耗时的。

进程池

从上面的例子可以看出,进程的创建和停止都是消耗资源的,所以进程绝不是越多越好。因为单个CPU核某时刻只能执行单个进程,所以最好的情况是将进程数量与CPU核数相等,这样可以最大化利用CPU。

这时就有一个问题出现了,进程数少还好说,进程数多了的话如何自动去维持一个固定的进程数目呢,这时候就要用到进程池了。进程池就是规定一个可容纳最大进程数目的池子,当池子中进程数目不足时自动添加新进程,从而将同时运行的进程数目维持在一个上限之内。这里的上限就应该是CPU的核数。

if __name__ == '__main__':
	from multiprocessing import Process, cpu_count, Pool
    print('main process is {}'.format(os.getpid()))
    print('core number is {}'.format(cpu_count()))  # 8
    start_time = time.time()
	### multiprocess pool
    p = Pool(8)
    for i in range(14):
        p.apply_async(func)
    p.close()
    p.join()
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

这里我首先利用cpu_count()方法计算了一下我这台电脑的CPU核数,8核,所以进程池的最大进程数目设定为8。

我电脑物理上是单CPU,4核。但是因为intel有超线程技术,一个核可以当作两个核来跑,所以逻辑上相当于8核

这里利用Pool类来创建进程池,传递一个参数是最大进程数。利用Pool对象的apply_async()方法往进程池中添加待执行的任务(注意不是进程,只是任务),这里也可以利用map_async(func,iterable)来添加,用来类似于内建的map()方法,不过需要待执行的函数带参数,类似下面这样

def func(n):
    print('process {} starts'.format(os.getpid()))
    time.sleep(n)
    print('process {} ends'.format(os.getpid()))
    
### multiprocess pool
    p = Pool(8)
    # for i in range(14):
    #     p.apply_async(func)
    p.map_async(func,range(14))
    p.close()
    p.join()

然后是close()方法,进程池不再接受新的任务(注意不是进程),以及terminate()方法,关闭主进程,此时未开始的子进程都不会执行了。同样的,想要让进程池去阻塞主进程可以用join()方法。注意join()一定要在close()或者terminate()之后

上面的程序执行结果如下

main process is 12860
core number is 8
process 11956 starts
process 34224 starts
process 10596 starts
process 20596 starts
process 27668 starts
process 15604 starts
process 10820 starts
process 16632 starts
process 11956 ends
process 11956 starts
process 34224 ends
process 34224 starts
process 10596 ends
process 10596 starts
process 20596 ends
process 20596 starts
process 27668 ends
process 27668 starts
process 15604 ends
process 15604 starts
process 10820 ends
process 16632 ends
process 11956 ends
process 34224 ends
process 10596 ends
process 20596 ends
process 27668 ends
process 15604 ends
total time is 5.258298635482788

一共14个任务,在最大数目为8的进程池里面至少要执行两轮,同时加上进程启动和停止的消耗,最后用时5.258秒。

这里顺便补充一下windows和linux如何查看每个cpu核的负载。

如果是windows系统,在任务管理器中,调到第二个Performance标签,在cpu曲线图上右击鼠标可以更改为逻辑cpu的负载图,如下

1-windows.png

如果是linux系统,通过top命令,然后按数字1就可以了,如下

2-linux.png

进程间通讯

前面说到进程间是相互独立的,不共享内存空间,所以在一个进程中声明的变量在另一个进程中是看不到的。这时候就要借助一些工具来在两个进程间进行数据传输了,其中最常见的就是队列了。

队列(queue)在生产消费者模型中很常见,生产者进程在队列一端写入,消费者进程在队列另一端读取。

首先创建两个函数,分别扮演生产者和消费者

def write_to_queue(queue):
    for index in range(5):
        print('write {} to {}'.format(str(index), queue))
        queue.put(index)
        time.sleep(1)


def read_from_queue(queue):
    while True:
        result = queue.get(True)
        print('get {} from {}'.format(str(result), queue))

这两个函数都接受一个队列作为参数然后利用put()方法往其中写入或者get()方法来读取。生产者会连续写入5个数字,每次间隔1秒,消费者则会一直尝试读取。

主程序如下

if __name__ == '__main__':
	from multiprocessing import Process, cpu_count, Pool
    print('main process is {}'.format(os.getpid()))
    print('core number is {}'.format(cpu_count()))  # 8
    start_time = time.time()
	### multiprocess queue
    from multiprocessing import Queue
    queue = Queue()
    pw = Process(target=write_to_queue, args=(queue,))
    pr = Process(target=read_from_queue, args=(queue,))
    pw.start()
    pr.start()
    pw.join()
    pr.terminate()
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

注意这里在创建子进程的时候就用元组的形式传递了参数,如果元组只有一个元素,记住添加逗号,否则会被认为是单个元素而不是元组。同时这里因为消费者是死循环,所以只是将生产者加入了阻塞,生产者进程执行完毕以后停止消费者进程。

最后打印结果如下

main process is 28268
core number is 8
write 0 to <multiprocessing.queues.Queue object at 0x0000023C6B25BF88>
get 0 from <multiprocessing.queues.Queue object at 0x000002EF410B1C88>
write 1 to <multiprocessing.queues.Queue object at 0x0000023C6B25BF88>
get 1 from <multiprocessing.queues.Queue object at 0x000002EF410B1C88>
write 2 to <multiprocessing.queues.Queue object at 0x0000023C6B25BF88>
get 2 from <multiprocessing.queues.Queue object at 0x000002EF410B1C88>
write 3 to <multiprocessing.queues.Queue object at 0x0000023C6B25BF88>
get 3 from <multiprocessing.queues.Queue object at 0x000002EF410B1C88>
write 4 to <multiprocessing.queues.Queue object at 0x0000023C6B25BF88>
get 4 from <multiprocessing.queues.Queue object at 0x000002EF410B1C88>
total time is 5.603313446044922

多线程

首先创建一个函数用于测试

import threading
def func2(n):
    print('thread {} starts'.format(threading.current_thread().name))
    time.sleep(2)
    print('thread {} ends'.format(threading.current_thread().name))
    return n

多线程使用的是threading.Thread

if __name__ == '__main__':
	print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    ### multithread
    t1 = threading.Thread(target=func2, args=(1,))
    t2 = threading.Thread(target=func2, args=(2,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

基本用法和上面进程的Process差不多,打印的结果如下

main thread is MainThread
thread Thread-1 starts
thread Thread-2 starts
thread Thread-1 ends
thread Thread-2 ends
total time is 2.002077341079712

对比前面多进程的2.38秒,这里还是快了不少的。

线程池

和进程一样,通常是使用线程池来完成自动控制线程数量的目的。但是这里就没有一个推荐的上限数量了,毕竟因为GIL的存在不管怎么样每次都只有一个线程在跑。

同时threading模块是不支持线程池的,python3.4以后官方推出了concurrent.futures模块来统一进程池和线程池的接口,这里关注一下线程池。

from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
if __name__ == '__main__':
	print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    ### threadpool
    executor = ThreadPoolExecutor(5)
    all_tasks = [executor.submit(func2, i) for i in range(8)]
    wait(all_tasks, return_when=ALL_COMPLETED)
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

这里利用ThreadPoolExecutor()创建一个线程池,最大上限为5,然后利用submit()方法往线程池中添加任务(注意是任务,不是线程),submit方法会返回一个future对象,注意这里我将创建的任务放进了一个列表中。

如果要阻塞主线程,不能用join方法了,需要用到wait()方法,该方法接受三个参数,第一个参数是一个future对象的列表,第二个参数是超时时间,这里放空,第三个参数是在什么时候结束阻塞,默认是ALL_COMPLETED表示全部任务结束之后,也可以设定为FIRST_COMPLETED表示第一个任务结束以后。

打印结果如下

main thread is MainThread
thread ThreadPoolExecutor-0_0 starts
thread ThreadPoolExecutor-0_1 starts
thread ThreadPoolExecutor-0_2 starts
thread ThreadPoolExecutor-0_3 starts
thread ThreadPoolExecutor-0_4 starts
thread ThreadPoolExecutor-0_0 ends
thread ThreadPoolExecutor-0_0 starts
thread ThreadPoolExecutor-0_2 ends
thread ThreadPoolExecutor-0_2 starts
thread ThreadPoolExecutor-0_1 ends
thread ThreadPoolExecutor-0_1 starts
thread ThreadPoolExecutor-0_3 ends
thread ThreadPoolExecutor-0_4 ends
thread ThreadPoolExecutor-0_0 ends
thread ThreadPoolExecutor-0_2 endsthread ThreadPoolExecutor-0_1 ends

total time is 4.003619432449341

最后的结果也是接近两倍的函数耗时4秒,比进程池快了不止一点点。

map

这里需要额外提一下多线程中的map方法。

多进程中的map_async()方法和多线程中的map()方法除了将任务加入线程池,还会按添加的顺序返回每个线程的执行结果,这个执行结果也很特殊,是一个生成器

from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
if __name__ == '__main__':
	print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    ### map
    executor = ThreadPoolExecutor(5)
    all_results = executor.map(func2, range(8))  # map返回的是线程执行的结果的生成器对象
    for result in all_results:
        print(result)
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

这里的all_results是一个生成器,可以通过for循环来按顺序获取每个线程的返回结果。同时值得注意的是map方法并不会阻塞主线程,也没法使用wait方法,只能通过获取生成器的结果来阻塞主线程了。

执行结果如下

main thread is MainThread
thread ThreadPoolExecutor-0_0 starts
thread ThreadPoolExecutor-0_1 starts
thread ThreadPoolExecutor-0_2 starts
thread ThreadPoolExecutor-0_3 starts
thread ThreadPoolExecutor-0_4 starts
thread ThreadPoolExecutor-0_0 ends
thread ThreadPoolExecutor-0_0 starts
0
thread ThreadPoolExecutor-0_1 ends
thread ThreadPoolExecutor-0_1 starts
thread ThreadPoolExecutor-0_2 ends
1
thread ThreadPoolExecutor-0_2 starts
2
thread ThreadPoolExecutor-0_3 ends
3
thread ThreadPoolExecutor-0_4 ends
4
thread ThreadPoolExecutor-0_0 ends
5
thread ThreadPoolExecutor-0_1 ends
6
thread ThreadPoolExecutor-0_2 ends
7
total time is 4.004628419876099

可以看出线程结果是按顺序返回的。

异步

想要不用map方法又要异步获取线程的返回值,还可以用as_completed()方法

from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, as_completed
if __name__ == '__main__':
	print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    executor = ThreadPoolExecutor(5)
    all_tasks = [executor.submit(func2, i) for i in range(8)]
    for future in as_completed(all_tasks):
        print(future.result())
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

as_completed接受一个任务列表做为参数,返回一个生成器,所以主线程依然会被阻塞,等所有线程执行完毕打印出结果再继续执行主线程。

打印结果如下

main thread is MainThread
thread ThreadPoolExecutor-0_0 starts
thread ThreadPoolExecutor-0_1 starts
thread ThreadPoolExecutor-0_2 starts
thread ThreadPoolExecutor-0_3 starts
thread ThreadPoolExecutor-0_4 starts
thread ThreadPoolExecutor-0_0 endsthread ThreadPoolExecutor-0_1 ends
thread ThreadPoolExecutor-0_1 starts
1
thread ThreadPoolExecutor-0_2 ends
thread ThreadPoolExecutor-0_2 starts
2

thread ThreadPoolExecutor-0_0 starts
thread ThreadPoolExecutor-0_3 ends
thread ThreadPoolExecutor-0_4 ends
0
3
4
thread ThreadPoolExecutor-0_1 ends
5
thread ThreadPoolExecutor-0_0 ends
7
thread ThreadPoolExecutor-0_2 ends
6
total time is 4.003146648406982

这里的线程结果就不是按照就不是按照添加任务的顺序,而是按照返回的先后顺序打印的。

所以,想要获取多线程的返回结果,按照添加顺序就用map方法,按照返回的先后顺序就用as_completed方法

想要更深入了解python中的futures模块,可以参考下面的文章学习下源码分析

https://www.jianshu.com/p/b9b3d66aa0be

同时python中还有专门做异步编程的asyncio模块,以后有时间再专门写文章说明。

线程间通讯

与多进程的内存独立不同,多线程间可以共享内存,所以同一个变量是可以被多个线程共享的,不需要额外的插件。想要让多个线程能同时操作某变量,要么将该变量作为参数传递到线程中(必须是可变变量,例如list和dict),要么作为全局变量在线程中用global关键字进行声明

因为有GIL的存在,每次只能有一个线程在对变量进行操作,有人就认为python不需要互斥锁了。但是实际情况却和我们想的相差很远,先看下面这个例子

def increase(var):
    global total_increase_times
    for i in range(1000000):
        var[0] += 1
        total_increase_times += 1


def decrease(var):
    global total_decrease_times
    for i in range(1000000):
        var[0] -= 1
        total_decrease_times += 1
        
        
if __name__ == '__main__':
    print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    var = [5]
    total_increase_times = 0
    total_decrease_times = 0
    t1 = threading.Thread(target=increase, args=(var,))
    t2 = threading.Thread(target=decrease, args=(var,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(var)
    print('total increase times: {}'.format(str(total_increase_times)))
    print('total decrease times: {}'.format(str(total_decrease_times)))
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

这里首先定义了两个函数,分别对传进来的list的第一个元素进行加一和减一操作,重复多遍。这里之所以使用list因为要满足可变变量的要求,对于python中变量和传参不熟悉的朋友可以参考另一篇博客《python3中各类变量的内存堆栈分配和函数传参区别实例详解》

然后在主线程中创建两个子线程分别运行,同时创建两个全局变量total_increase_timestotal_decrease_times分别来统计对变量进行加值和减值的次数,为了防止可能由于操作次数不一致导致的错误。

打印结果如下

main thread is MainThread
[281970]
total increase times: 1000000
total decrease times: 1000000
total time is 0.7370336055755615

很奇怪,对变量值增加和减少同样的次数,最后的结果却和原先的值不一致。而且如果将该程序重复运行多次,每次得到的最终值都不同,有正有负

这是为什么呢?

这是因为某些在我们看来是原子操作的,例如+或者-,在python看来不是的。例如执行a+=1操作,在python看来其实是三步:获取a的值,将值加1,将新的值赋给a。在这三步中的任意位置,该线程都有可能被暂停,然后让别的线程先运行。这时候就有可能出现如下的局面

线程1获取了a的值为10,被暂停
线程2获取了a的值为10
线程2将a的值赋值为9,被暂停
线程1将a的值赋值为11,被暂停
线程2获取了a的值为11
...

这样线程1就将线程2的操作全部覆盖了,这也就是为什么最后的结果有正有负。

那么如何处理这种情况呢?

需要用到互斥锁。

互斥锁

线程1在操作变量a的时候就给a上一把锁,别的线程看到变量有锁就不会去操作该变量,一直到线程1再次获得GIL之后继续操作将锁释放,别的线程才有机会对该变量进行操作。

修改下上面的代码

def increase(var, lock):
    global total_increase_times
    for i in range(1000000):
        if lock.acquire():
            var[0] += 1
            lock.release()
            total_increase_times += 1


def decrease(var, lock):
    global total_decrease_times
    for i in range(1000000):
        if lock.acquire():
            var[0] -= 1
            lock.release()
            total_decrease_times += 1
            
            
if __name__ == '__main__':
    print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    lock = threading.Lock()
    var = [5]
    total_increase_times = 0
    total_decrease_times = 0
    t1 = threading.Thread(target=increase, args=(var, lock))
    t2 = threading.Thread(target=decrease, args=(var, lock))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(var)
    print('total increase times: {}'.format(str(total_increase_times)))
    print('total decrease times: {}'.format(str(total_decrease_times)))
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

这里创建了一个全局锁lock并传递给两个线程,利用acquire()方法获取锁,如果没有获取到锁该线程会一直卡在这,并不会继续循环,操作完毕用release()方法释放锁。

打印结果如下

main thread is MainThread
[5]
total increase times: 1000000
total decrease times: 1000000
total time is 2.1161584854125977

最终的结果不管执行多少次都没有问题,但是因为前面说的等待锁的过程会造成大量时间的浪费,这里耗时2.116秒比前面的0.737秒要慢了3倍。

队列

多线程间通讯也可以用queue,因为queue是对线程安全的,不需要额外加锁了

from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, as_completed
if __name__ == '__main__':
	print('main thread is {}'.format(threading.current_thread().name))
    start_time = time.time()
    ### multithread queue
    from queue import Queue
    queue = Queue()
    tw = threading.Thread(target=write_to_queue, args=(queue,))
    tr = threading.Thread(target=read_from_queue, args=(queue,))
    tr.setDaemon(True)
    tw.start()
    tr.start()
    tw.join()
    end_time = time.time()
    print('total time is {}'.format(str(end_time - start_time)))

这里不能像进程中那样用terminate方法停止一个线程,需要用setDaemon方法。

打印结果如下

main thread is MainThread
write 0 to <queue.Queue object at 0x000001E3DACD21C8>
get 0 from <queue.Queue object at 0x000001E3DACD21C8>
write 1 to <queue.Queue object at 0x000001E3DACD21C8>
get 1 from <queue.Queue object at 0x000001E3DACD21C8>
write 2 to <queue.Queue object at 0x000001E3DACD21C8>
get 2 from <queue.Queue object at 0x000001E3DACD21C8>
write 3 to <queue.Queue object at 0x000001E3DACD21C8>
get 3 from <queue.Queue object at 0x000001E3DACD21C8>
write 4 to <queue.Queue object at 0x000001E3DACD21C8>
get 4 from <queue.Queue object at 0x000001E3DACD21C8>
total time is 5.00357986831665

扩展

多进程间的变量共享也可以用类似多线程那样传递变量或者全局变量的方式,限于篇幅这里没有展开说,感兴趣的朋友可以参考知乎上一篇不错的文章https://zhuanlan.zhihu.com/p/68828849

总结

总结下文章中涉及的知识点

  • CPU密集型使用多进程,IO密集型使用多线程

  • 查看进程ID和线程ID的命令分别是os.getpid()threading.current_thread()

  • 多进程使用multiprocessing就可以了,通常使用进程池来完成操作,阻塞主进程使用join方法

  • 多线程使用threading模块,线程池使用concurrent.futures模块,同时主线程的阻塞方法有多种

  • 不管多进程还是多线程,生产消费模型都可以用队列来完成,如果要用多线程操作同一变量记得加锁

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python多进程和多线程看这一篇就够了 的相关文章

  • .numpy()、.item()、.cpu()、.clone()、.detach()及.data的使用 && tensor类型的转换

    文章目录 numpy item cpu clone detach 及 data的使用 item cpu numpy clone detach data data和 detach 不同点 Tensor类型的转换 numpy item cpu
  • 如何在Anaconda安装Pygame

    开始之前 先来安装Pygame 可使用pip模块来帮助下载安装python包 要安装Pygame 需在终端提示符下执行如下命令 python m pip install user pygame 对于下载了anaconda用户 可按以下操作
  • python字符串中所有符合条件的索引

    使用re库中的finditer import re s 1111ah11111ah test re finditer ah s print i for i in test
  • python通过ssl加密连接mysql

    目录 1 django程序的配置 2 pymysql连接数据库 3 DBUtils数据库连接池连接配置 我们在连接某些数据库时 需要提供ssl证书 如果是IT的数据库 那么可能会提供下载 如果是自己想做 可参考如下步骤 https dev
  • 【Python 1-10】Python手把手教程之——一篇讲透if语句以及if语句的特殊用法

    作者 弗拉德 来源 弗拉德 公众号 fulade me if 简单示例 假设你有一个汽车列表 并想将其中每辆汽车的名称打印出来 对于大多数汽车 都应以首字母大写的方式打印其名称 但对于汽车名 bmw 应以全大写的方式打印 下面的代码遍历一个
  • 最全面的Python重点知识汇总,建议收藏!

    这是一份来自于 SegmentFault 上的开发者 二十一 总结的 Python 重点 由于总结了太多的东西 所以篇幅有点长 这也是作者 缝缝补补 总结了好久的东西 Py2 VS Py3 print成为了函数 python2是关键字 不再
  • 序列化pickle&json模块

    序列化pickle json模块 序列化 序列化是指把内存里的数据类型转变成字符串 以使其能存储到硬盘或通过网络传输到远程 因为硬盘或网络传输时只能接受bytes 用于序列化的两个模块 json 用于字符串 和 python数据类型间进行转
  • 自学笔记-Python基础09--第三方库的概念及操作

    库 具有相关功能模块的集合 python的一大特色就是拥有强大的库 库可以分为三种 1 标准库 python自带的 无需安装直接使用 2 第三方库 由他人提供的 使用时需要先安装 3 自定义库 自己写的模块 自己用 标准库 想看python
  • python基础系列之元组

    元组应用场景 储存多个数据 但是这些数据不可修改 我们知道列表可以储存多个数据 但是数据可增加 修改 删除 这也是元组和列表不一样的地方 如何定义一个元组 多个数据元组 t1 10 20 30 单个数据元组 t2 10 注意在定义单个数据的
  • 三、Python基础(高级变量类型篇)

    三 Python基础 高级变量类型篇 目录 三 Python基础 高级变量类型篇 一 列表 list 1 列表的定义 2 列表的常用方法和操作 对象 方法名 参数 3 for in对列表迭代遍历 4 利用 for in 的输出技巧 5 se
  • 零基础小白入门Python,值得看的几本书籍

    Python目前是全球4大流行编程语言之一 根据今年TIOBE最新排名 Python已超越C 与Java C C 一起成为全球前4大最流行语言 指数变化一直呈现出上升的趋势 TIOBE最新排名 众所周知 Python应用广泛 涵盖后端开发
  • 职场上会用Python的人到底有多牛?

    这个人工智能崛起的时代 似乎人人都在聊 Python 从硬件的芯片层面 物联网 一路杀到云端 大数据 人工智能这些炙手可热的领域 无论什么领域 只要它需要编程 都会有Python的身影 下面就和大家一起来聊聊 Python 的好 到底它牛在
  • Python编程基础之三对象

    一 简介 Python使用对象模型来存储数据 构造任何类型的值都是一个对象 再加上内建类型 标准类型运算符和内建函数 有助于更好的理解Python是如何工作的 二 详解 1 Python的对象 所有的 Python 对像都拥有三个特性 身份
  • Python简单的用户交互

    death age 80 name input your name input 接受的所有数据都是字符串 即便你输入的是数字 但依然会被当成字符串来处理 age input your age print type age int integ
  • 相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。

    题目描述 相传韩信才智过人 从不直接清点自己军队的人数 只要让士兵先后以三人一排 五人一排 七人一排地变换队形 而他每次只掠一眼队伍的排尾就知道总人数了 输入3个非负整数a b c 表示每种队形排尾的人数 a lt 3 b lt 5 c l
  • Python中的None

    一 None None是python中的一个特殊的常量 表示一个空的对象 数据为空并不代表是空对象 例如空列表 等都不是None None有自己的数据类型NontType 你可以将None赋值给任意对象 但是不能创建一个NoneType对象
  • 4.函数、模块与包

    文章目录 一 函数 二 模块与包 引用 一 函数 Python 使用 def 关键字来声明函数 格式如下所示 def 函数名 参数 函数体 return 返回值 如果要定义一个无任何功能的空函数 函数体只写 pass 即可 def 函数名
  • python---函数名的使用

    函数名的多种用法 函数名当作变量名赋值 函数名当作函数的实参 函数名当作函数的返回值 函数名当作容器类型的元素 函数名当作变量名赋值 def index print from function index print index res i
  • python 中 os._exit(), sys.exit()

    1 os exit 不抛异常 后面的代码就不执行了 不执行相关清理工作 直接退出 Python 解释器一般来说用在子线程中退出 2 sys exit 引发一个 SystemExit 异常 没有捕获这个异常 会直接退出 捕获这个异常可以做一些
  • mayavi TypeError: unsupported operand type(s) for -: ‘str‘ and ‘str‘

    目录 mayavi安装报错 模拟异常 TypeError unsupported operand type s for str and str 模拟异常 TypeError unsupported operand type s for st

随机推荐

  • 从Random Walk(随机游走)到Graph Embedding(DeepWalk,LINE,Node2vec,SDNE,Graph2vec,GraphGAN)

    前言 本文转载自csdn博主上杉翔二系列博客并外加一些自己收集的资料 在这里仅作为自己学习之用 原文链接 https blog csdn net qq 39388410 article details 87904974 1 随机游走 普通数
  • idea java 插件开发_Intellij IDEA插件开发入门详解

    现今的IDE尽管有如 洪水猛兽 般强大 但要知道再强大的IDE也没法提供给使用者想要的一切功能 所以IDE一般都提供有API接口供开发者自行扩展 下面以Intellij IDEA 12下的插件开发为例 来看一下如何进一步增强IDE以适应开发
  • ROS自学实践(6):ROS进行激光SLAM建图——gmapping

    本节主要记录运行ROS自带的SLAM建模包gmapping方法 为后续理解这些代码 建立自己的SLAM算法打下基础 基于粒子滤波算法 二维栅格地图 需要里程计信息 1 通过命令行安装gmapping包 sudo apt get instal
  • win10下qt 中没有代码提示框了怎么办?

    在这里我也找了好久 发现是跟你装的输入法有冲突了 所以代码提示没有了 请你切换到英文的输入下 把你的输入法换成标准的英文输入输入状态 图片如下 换成这样就可以提示了 如图所示完美解决不能提示的问题 好了完美解决问题 在这里我放上我讲的几个课
  • Elasticsearch搜索详解(六):文本检索

    文本检索是关系型数据库 如 MySQL 的弱项 而恰恰是 ES 的强项 前一篇文章已经提到了 match term 除此之外还有multi match match phrace 等 分别的含义是 match 从一个字段中检索关键字 包括模糊
  • react中setState即时更新解决方案

    博主在做一个前端项目时 需要根据props中的状态来修改state中的状态 由于react中setState更新状态不能及时显示到页面 博主总结如下可及时更新state中的方法 1 componentWillReceiveProps 2 g
  • Mybatis:xml配置和基本增删改查

    目录 一 环境配置 environments 1 事务管理器 transactionManager 2 数据源 dataSource 3 属性 property 4 设置 settings 5 类型别名 typeAliases 二 安装My
  • net.ipv4.tcp_tw_reuse是干嘛的?

    文章目录 前言 准备工作 sd01的配置 sd02的配置 开始测试 关闭net ipv4 tcp tw reuse 打开net ipv4 tcp tw reuse 关闭客户端的net ipv4 tcp timestamps 关闭服务器端的n
  • Nacos+Node基础教程

    简介 Nacos是一个更易于构建云原生应用的动态服务发现 配置管理和服务管理平台 功能 动态配置服务 动态配置服务让您能够以中心化 外部化和动态化的方式挂历所有环境的配置 动态配置消除了配置变更时 重新部署应用和服务的需要 配置中心化管理让
  • 如何将子窗口的值传到父窗口去调用

    这是我当初的问题 现在我想实现这样一个功能 现在父窗口有一个select控件 同时有一个 增加 按钮 点击按钮 弹出一个窗口 这时弹出窗口也有一个table 同时有一个 确认 按钮 table中有若干项 每一行对应一条记录 并有一个chec
  • 前端VUE项目部署到远程服务器

    文章目录 1 基础介绍 2 准备VUE项目 3 服务器安装 nginx服务器 4 启动nginx 5 修改nginx 配置 6 打包部署VUE项目 1 基础介绍 VUE项目 前后端分离 前后端部署到同一个服务器上 服务器 腾讯云轻量应用服务
  • 关于order by后面接条件查询

    适用场景 如表tab a 有三个字段 如果field1非空则按升序排列 如果field1是空再排field2 如果 field2非空升序排列 如果field2是空再排field3 如果field3非空则升序排列 如果field3是空 例子1
  • linux+应用程序运行日志,Linux 系统运行着许多子系统和应用程序。您可以使用系统日志记录从启动时就收集有关运行中系统的数据。有时...

    概述 在本教程中 您将学习以下内容 配置 syslog 守护程序 了解标准设施 优先级和操作 配置日志轮换 了解 rsyslog 和 syslog ng 系统内部发生了什么 Linux 系统运行着许多子系统和应用程序 您可以使用系统日志记录
  • c++ 双端队列 deque用法解析

    1 deque的作用 deque即双端队列 它的用法非常强大 可以代替栈stack 队列queue 向量容器vector等等 因为它能像栈一样后进先出 也能像queue一样先进先出 还能像vector一样随机访问 同时支持sort lowe
  • java模糊查询代码_Java模糊查询方法详解

    这篇文章主要为大家详细介绍了Java模糊查询方法的实现 实例教你如何用Java做模糊查询结果 感兴趣的小伙伴们可以参考一下 当我们需要开发一个方法用来查询数据库的时候 往往会遇到这样一个问题 就是不知道用户到底会输入什么条件 那么怎么样处理
  • 【机器学习】太香啦!只需一行Python代码就可以自动完成模型训练!

    自动化机器学习 Auto ML 是指数据科学模型开发的管道组件自动化 AutoML 减少了数据科学家的工作量并加快了工作流程 AutoML 可用于自动化各种管道组件 包括数据理解 EDA 数据处理 模型训练 超参数调整等 对于端到端机器学习
  • 小米 adb 驱动_ADB禁用系统应用

    第一步 下载ADB压缩包 并解压到根目录下 自己百度找ADB包 第二步 在ADB目录下 shift 鼠标右键 打开powershell 并输入cmd 第三步 手机进入开发者模式 百度 并打开USB调试 数据线连接电脑和手机 要安装好驱动 在
  • 解决idea项目没有蓝色小方块

    导入项目后 把项目中的几个子moudle复制了一份 作为一个新模块 结果发现 项目右下角没有 蓝色小方块 因此造成maven不能识别 如下图 解决方式 在右边侧栏 maven 面板 点击 选择该项目中的pom xml文件即可
  • linux安装时 dev sda4,VMvare在CentOS7.4安装iscsi共享盘

    VMvare 在 CentOS7 4 安装 iscsi 共享盘 1 在节点 1 上添加一个 20g 的存储并 reboot 1 1 查看新添加的磁盘 root xmc1 fdisk l Disk dev sda 32 2 GB 322122
  • python多进程和多线程看这一篇就够了

    脑海中关于进程和线程的概念一直很模糊 什么时候该用多进程 什么时候该用多线程总是搞不清楚 同时python因为历史遗留问题存在GIL全局锁 就让人更加困惑 这一篇就完整整理一下python中进程和线程的概念和实现 文章目录 进程和线程 GI