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多进程和多线程看这一篇就够了 的相关文章

  • 【python基础】猜数字游戏

    前言 相信很多人都玩过猜数字游戏 游戏规则也十分简单 还记得小时候我经常和朋友一起玩 我们在桌上摆放一些1 100以内的数字卡片 一个人随机抽取一张卡片 这里不许偷看卡片内容 首先这个人随机猜一个数字 然后其他人会告诉你猜大了还是猜小了 先
  • .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基础(3)——PyCharm介绍

    Python基础 3 PyCharm介绍 文章目录 Python基础 3 PyCharm介绍 课程目标 一 PyCharm的作用 二 下载和安装 2 1 下载 2 2 安装 三 PyCharm基本使用 3 1 新建项目 3 2 新建文件并书
  • 基础知识汇总(python)

    1 注释 单行注释 多行注释 2 换行 total applePrice orangePrice milkPrice 3 变量 111 赋值给了 a a 111 变量名规则 字母 数字 下划线组成 数字不可以开头 不能关键字重用 变量名必须
  • 最全面的Python重点知识汇总,建议收藏!

    这是一份来自于 SegmentFault 上的开发者 二十一 总结的 Python 重点 由于总结了太多的东西 所以篇幅有点长 这也是作者 缝缝补补 总结了好久的东西 Py2 VS Py3 print成为了函数 python2是关键字 不再
  • python---迭代器, for循环底层原理

    目录 何为迭代 可迭代对象 迭代器对象 迭代器的优缺点 优点 缺点 for循环底层原理 迭代取值与索引取值对比 何为迭代 迭代器即用来迭代取值的工具 而迭代是重复反馈过程的活动 其目的通常是为了逼近所需的目标或结果 每一次对过程的重复称为一
  • python基础系列之元组

    元组应用场景 储存多个数据 但是这些数据不可修改 我们知道列表可以储存多个数据 但是数据可增加 修改 删除 这也是元组和列表不一样的地方 如何定义一个元组 多个数据元组 t1 10 20 30 单个数据元组 t2 10 注意在定义单个数据的
  • Python中类成员变量与实例成员变量相互影响的原因超详细解释

    今天在看python学习手册时看到了两句话 一 第26章中 类对象提供默认行为 二 第26章中 实例对象是具体的元素 书中给的例子是这样的 但上网查了一下好像第二句话不是非常准确 如下面的文章 原文 https www jb51 net a
  • Python之sys模块详解

    sys模块 sys 模块提供了许多函数和变量来处理 Python 运行时环境的不同部分 常见用法 sys argv 命令行参数List 第一个元素是程序本身路径 sys modules keys 返回所有已经导入的模块列表 sys exc
  • 2023年IT行业就业前景分析,准职场人必看!

    随着疫情的放开 2022已接近尾声 新的一年即将来临 作为打工人最关心的肯定是2023年的就业市场以及行业未来发展前景 如何最直观地看待这个行业是否还有前景 最好的方式就是看市场需求 作为准职场人的你 速速关注起来 根据智联招聘10月发布的
  • 【Python】工程与包(2)

    创建工程及第三方包管理 New environment using 新建的项目里有一个venv virtualenv 文件夹 专门存放本项目所依赖的第三方模块 Existing interpreter 表示新建的项目所依赖的第三方模块是存放
  • python的赋值操作浅析

    目录 前言 一 不可变类型的赋值 1 Numbers的赋值 2 String类型的赋值 3 Tupes类型赋值 4 函数传参赋值 二 可变类型的赋值 1 List赋值 2 函数传参 总结 前言 python中Numbers 数
  • Python-字典:键值对的魔法世界

    深入理解Python字典 键值对的魔法世界 在Python中 字典 Dictionary 是一种强大且常用的数据结构 它允许我们存储和组织键值对 Key Value 数据 与列表和元组不同 字典中的数据是无序的 但每个数据都与一个唯一的键相
  • Mysql数据库的环境搭建【详细】

    作者简介 大学机械本科 野生程序猿 学过C语言 玩过前端 还鼓捣过嵌入式 设计也会一点点 不过如今痴迷于网络爬虫 因此现深耕Python 数据库 seienium JS逆向 安卓逆向等等 目前为全职爬虫工程师 学习的过程喜欢记录 目前已经写
  • python opencv卡尺测量边缘距离

    opencv 卡尺法 测量边缘距离 参考来源 https github com crackwitz metrology demo 前言 一 测量方法 二 测量步骤 1 获取直线的像素 2 高斯滤波平滑曲线 3 计算跳变幅度值 4 计算距离值
  • Python中的None

    一 None None是python中的一个特殊的常量 表示一个空的对象 数据为空并不代表是空对象 例如空列表 等都不是None None有自己的数据类型NontType 你可以将None赋值给任意对象 但是不能创建一个NoneType对象
  • Pycharm官网下载安装

    下载链接 pycharm官网 https www jetbrains com pycharm 然后来到这个界面 点击Download 下载按钮 然后点击开源版本 Community 下载安装就好了 接下来就创建项目 点击Create 这样就
  • 即刻掌握python格式化输出的三种方式 (o゜▽゜)o☆

    目录 1 f 转化的格式化输出方式 2 格式化输出的方法 3 format 格式化输出的方法 1 f 转化的格式化输出方式 只需要在我们要格式化输出的内容开头引号的前面加上 f 在字符串内要转义的内容用 括起来即可 模板 print f x
  • 求你了!别再问我怎么学 Python 了

    很多小伙伴问如何学习 Python 哪里可以找到实战的 Python 项目 有没有爬虫案例等等 今天给大家分享一份我整理的 Python 项目大全学习资料 文末有获取方式 话不多说 直接上干货 首先 全部资料目录压缩 由于内容较多 这里也仅

随机推荐

  • 从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