你所谓的鸡肋?Python中的多进程、多线程和协程

解决Python多进程/线程错误 多进程和多线程是Python中常用的并发编程技术,它们可以提高程序的执行效率和响应性。然而,在使用多进程/线程时,可能会遇到一些常见的错误。本文将探讨一些常见的多进程/线程错误,并提供相应的解决方案和示例代码。错误:资源竞争(Resource contention)多个进程/线程同时访问共享资源,导致竞争条件和数据一致性问题。错误:死锁(Deadlock)多个进程/线程相互等待对方释放资源,导致程序无法继续执行。错误:数据竞争(Data race) 阅读详情

本文撰写过程中参考了胡俊峰老师《Python程序设计与数据科学导论》课程的内容。

概念:

  1. 并发:一段时间内同时推进多个任务,但不一定要在一个时刻同时进行多个任务。
  2. 并行:一段时间内同时推进多个任务,且在一个时刻要同时进行多个任务。
  3. 并行是并发的子集;单核CPU交替执行多个任务是并发但不是并行;多核CPU同时执行多个任务既是并发也是并行。

何时需要并发?

  • 需要同时处理多个任务
  • 经常需要等待资源
  • 多个子过程互相协作

电脑执行任务的机制:

  • 操作系统内核 负责任务(i.e. 进程/线程)的挂起与唤醒,和资源的分配(比如一个程序能访问哪些内存地址)
  • 进程是资源分配的最小单元,不同进程之间不共享资源(比如可访问的内存区域);进程可再分为线程,线程之间共享大部分资源。

正是因为 是否共享资源 上的区别,线程间的切换(即挂起和唤醒)比进程间的切换更快。

线程是调度执行的最小单元。这意味着操作系统内核会负责将多个线程并发执行。

多进程和多线程的比较

多进程:

  • 将任务拆分为多个进程来进行
  1. 由内核决定是并行还是仅仅并发。
  • 进程间不共享内存
  1. 优点:一个进程挂了不影响别的

  2. 缺点:切换进程耗时大、进程间通信不便

多线程:

  • 将任务拆分为一个进程内的多个线程来进行
  1. 由内核决定是并行还是仅仅并发。

  2. 在CPython解释器中有全局解释器锁,导致多线程只能并发而不能并行(多进程可以并行)。

  • 进程间共享内存
  1. 优点:切换耗时低、通信方便

  2. 缺点:在并行时对全局变量要使用锁机制
    锁机制:一个线程使用一个全局变量时,先等待其(被其他线程)解锁,再将其上锁,再使用,用后再解锁。

如果不使用锁的话:100个a+=1的线程执行完成后(初始a=0),a可能<100 。

数据科学中可以为了提高效率而不使用锁机制,但同时要容忍由此带来的差错。

多进程的机制和代码实现

以下介绍的函数中,几乎每一个有阻塞可能的,都会有一个可选的timeout参数。这件事将不再重提。

基本用法

from multiprocessing import Process
import os
import time

def task(duration, base_time):
    pid = os.getpid()
    print(f'son process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s with parameter {duration}')
    time.sleep(duration)
    print(f'son process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s')

if __name__ == '__main__':
    pid = os.getpid()
    base_time = time.perf_counter()
    print(f'main process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s')
    p1 = Process(target=task, args=(1,base_time)) # a process that executes task(1,base_time); currently not running
    p2 = Process(target=task, args=(2,base_time)) # a process that executes task(2,base_time); currently not running
    p1.start()
    p2.start()
    print(p1.is_alive(), p2.is_alive()) # whether they are running
    print('main process can proceed while son processes are running')
    p1.join() # wait until p1 finishes executing (the main process will pause on this command in the meantime) and kill it after it finishes
    p2.join() # wait until p2 finishes executing (the main process will pause on this command in the meantime) and kill it after it finishes
    print(f'main process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s')
main process id 3316 starts at 0.000001s
True True
main process can proceed while son processes are running
son process id 15640 starts at 0.002056s with parameter 1
son process id 10716 starts at 0.003030s with parameter 2
son process id 15640 ends at 1.002352s
son process id 10716 ends at 2.017861s
main process id 3316 ends at 2.114324s

如果没有p1.join()和p2.join(),主进程会在很短的时间内完成执行,而此时子进程仍在运行。输出结果如下:

main process id 11564 starts at 0.000001s
True True
main process can proceed while son processes are running
main process id 11564 ends at 0.011759s
son process id 13500 starts at 0.004392s with parameter 1
son process id 11624 starts at 0.003182s with parameter 2
son process id 13500 ends at 1.009420s
son process id 11624 ends at 2.021817s

为何0.004秒的事件在0.003秒的事件之前被输出?

1.因为print语句的耗时未被计算在内
2.因为perf_counter()在Windows下有bug,它给出的时间在不同进程之间不完全同步。

需要注意,一个子进程结束运行后仍然处于存活状态;只有被join()之后才会正式死亡(即被从系统中除名)。

关于if __name__ == '__main__'::

  • 在Python中,可以通过import来获取其他文件中的代码;在文件B的开头(其他位置同理)import文件A,相当于把A在B的开头复制一份。

  • 如果在复制A的内容时,我们希望A中的一部分代码在执行时被忽略(比如说测试语句),就可以给A中的这些代码加上if __name__ == '__main__':

  1. 对于从别处import来的代码,系统变量__name__在这段代码中会等于来源文件的名字(或模块名,这你不用在意);对于存在于本文件中的代码,__name__会等于__main__。
  • 由于某些原因,在Windows下,如果一个文件的代码中使用了多进程,则这个文件中会隐式地import自己(一次或多次);将所有零级缩进的代码放在if name == ‘main’:中,可以避免产生重复执行的问题(注意到如果不这样做的话,import来的副本中还会再次import自身,导致无限递归import并报错)。
  1. 暂时可以认为,采取这一措施后就可完全消除“隐式import自身”所产生的效应。

进程复制

from multiprocessing import Process
import os

pid = os.getpid()

def task():
    global pid
    print(pid)
    pid = 1
    print(pid)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()
    print(pid)

在Windows下的输出:

4836
1
2944
511
1
511

前两个数都是由子进程输出,第三个数由父进程输出。

  • 注意到pid在子进程中被赋为1后,在父进程中并不是1。这说明,子进程的target函数中对运行环境的修改,不影响父进程的运行环境。事实上,反之也是成立的(父不影响子)。也就是说,一旦子进程的运行环境完成创建之后,父进程的运行环境与子进程的运行环境之间就完全独立。
  1. 由于这个独立性,子进程的运算结果也无法直接反馈给父进程。稍后会介绍两种解决方式:1. 进程间通信 2. 利用 进程池apply方法的返回值。
  • 注意到一三行的输出在Windows下不同,而在Linux下相同。这说明,子进程中全局变量pid的取值,在Linux下是直接复制父进程中pid的取值而得到的,在Windows下是通过重新运行pid = os.getpid()而得到的。更一般地,有以下这两个事实:
  1. 在Windows中,Process(target)创建出的子进程是一张白纸(即运行环境空空如也);当调用start()的时候,它会先通过import语句来将父进程的整个代码文件完整执行一遍(从而创建出一个新的运行环境),然后再开始运行target函数。所以,if __name__ == '__main__':包起来的代码,就只会被父进程执行;而未被包起来的零级缩进代码,则也会被每个子进程(在自己的运行环境里)各自执行一遍。
    这就是之前提到的“隐式import自身”的机制。

  2. 在Linux中,Process(target)创建出的子进程,会全盘复制父进程的运行环境,而不会自己重新创建。复制出来的子进程运行环境,与父进程的运行环境完全独立。

from multiprocessing import Process
import os

def task():
    pass

if __name__ == '__main__':
    p = Process(target=task)
    print('son process created')
    p.start()
    print('son process starts')
    p.join()
    print('son process ends')

print('gu?')

在Windows下的输出

son process created
son process starts
gu?
son process ends
gu?

由此可见,Windows下子进程(在初始化时)在执行父进程的代码文件时,父进程中son_process.start()以后的内容(比如print('gu?'))也会被执行。

进程池

如果我们有很多的任务要同时进行,为每个任务各开一个进程既低效(创建和销毁进程开销大、无法全部并行、内核难以调度)又可能不被内核允许。

解决方案:使用进程池,池中放着几个进程(一般不会比CPU的核数多很多),有新任务时找一个空闲进程分配给它,没有空闲进程则等待。缺点是没有空闲进程时需要等待,因此不能算是完全的并发。

进程池的基本用法

from multiprocessing import Pool
import os, time

def task(duration, base_time, task_name):
    pid = os.getpid()
    print(f'son process id {pid} starts working on {task_name} at {"%.6f" % (time.perf_counter()-base_time)}s with parameter {duration}')
    time.sleep(duration)
    print(f'son process id {pid} ends working on {task_name} at {"%.6f" % (time.perf_counter()-base_time)}s')

if __name__ == '__main__':
    pid = os.getpid()
    base_time = time.perf_counter()
    print(f'main process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s')
    pool = Pool(3) # a pool containing 3 subprocesses
    print('start assigning tasks')
    for i in range(4):
        pool.apply_async(task, args=(1, base_time, "TaskNo."+str(i+1))) # assign task to some process in pool and start running
                                                                        # if all son processes are busy, wait until one is free and then start
    pool.close() # no longer accepting new tasks, but already applied ones (including those that are waiting) keeps running.
    print('all tasks assigned; wait for son processes to finish')
    pool.join() # wait until all tasks are done, and then the pool is dead. `join()` can be called only if `close()` has already been called
    print(f'all tasks finished at {"%.6f" % (time.perf_counter()-base_time)}s')
    print(f'main process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s')

输出:(Win和Linux下输出相似)

main process id 5236 starts at 0.000002s
start assigning tasks
all tasks assigned; wait for son processes to finish
son process id 8724 starts working on TaskNo.1 at 0.030557s with parameter 1
son process id 14584 starts working on TaskNo.2 at 0.037581s with parameter 1
son process id 10028 starts working on TaskNo.3 at 0.041210s with parameter 1
son process id 14584 ends working on TaskNo.2 at 1.042662s
son process id 8724 ends working on TaskNo.1 at 1.040211s
son process id 14584 starts working on TaskNo.4 at 1.044109s with parameter 1
son process id 10028 ends working on TaskNo.3 at 1.054017s
son process id 14584 ends working on TaskNo.4 at 2.055515s
all tasks finished at 2.214534s
main process id 5236 ends at 2.214884s

当使用apply_async(“异步调用”)添加任务时,主进程在子进程执行任务期间会继续运行;如果用apply(“同步调用”)添加任务,则主进程会暂停(“阻塞”)直到该任务完成。一般使用apply_async而不是apply。

进程池中的进程复制

from multiprocessing import Pool
import os, time

all_tasks_on_this_son_process = []

def task(duration, base_time, task_name):
    global all_tasks_on_this_son_process
    pid = os.getpid()
    print(f'son process id {pid} starts working on {task_name} at {"%.6f" % (time.perf_counter()-base_time)}s with parameter {duration}, this process already executed',all_tasks_on_this_son_process)
    time.sleep(duration)
    print(f'son process id {pid} ends working on {task_name} at {"%.6f" % (time.perf_counter()-base_time)}s')
    all_tasks_on_this_son_process += [task_name]

if __name__ == '__main__':
    pid = os.getpid()
    base_time = time.perf_counter()
    print(f'main process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s')
    pool = Pool(3)
    print('start assigning tasks')
    for i in range(4):
        pool.apply_async(task, args=(1, base_time, "TaskNo."+str(i+1)))
    pool.close()
    print('all tasks assigned; wait for son processes to finish')
    pool.join()
    print(f'all tasks finished at {"%.6f" % (time.perf_counter()-base_time)}s')
    print(f'main process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s')

print('gu?')

Windows下输出:

main process id 6116 starts at 0.000001s
start assigning tasks
all tasks assigned; wait for son processes to finish
gu?
gu?
gu?
son process id 16028 starts working on TaskNo.1 at 0.037577s with parameter 1, this process already executed []
son process id 11696 starts working on TaskNo.2 at 0.041393s with parameter 1, this process already executed []
son process id 5400 starts working on TaskNo.3 at 0.038409s with parameter 1, this process already executed []
son process id 11696 ends working on TaskNo.2 at 1.041521s
son process id 16028 ends working on TaskNo.1 at 1.038722s
son process id 11696 starts working on TaskNo.4 at 1.042543s with parameter 1, this process already executed ['TaskNo.2']
son process id 5400 ends working on TaskNo.3 at 1.052573s
son process id 11696 ends working on TaskNo.4 at 2.053483s
all tasks finished at 2.167447s
main process id 6116 ends at 2.167904s
gu?

在Windows下,池中的每个线程会在(且仅在)它分配到的的第一个任务将要开始执行时,运行一遍父进程的代码以构建运行环境。一个进程在前一个任务中对运行环境的改变,会原样体现在下一个任务的运行环境里。(即接受新任务的时候会直接继续使用上一个任务遗留下的运行环境)

Linux下输出:

main process id 691 starts at 0.000001s
all tasks assigned; wait for son processes to finish
son process id 692 starts working on TaskNo.1 at 0.104757s with parameter 1, this process already executed []
son process id 693 starts working on TaskNo.2 at 0.104879s with parameter 1, this process already executed []
son process id 694 starts working on TaskNo.3 at 0.105440s with parameter 1, this process already executed []
son process id 692 ends working on TaskNo.1 at 1.106427s
son process id 693 ends working on TaskNo.2 at 1.106426s
son process id 694 ends working on TaskNo.3 at 1.107157s
son process id 692 starts working on TaskNo.4 at 1.107560s with parameter 1, this process already executed ['TaskNo.1']
son process id 692 ends working on TaskNo.4 at 2.110033s
all tasks finished at 2.117158s
main process id 691 ends at 2.117452s
gu?

在Linux下,池中的每个线程会在(且仅在)它的第一个任务将要开始执行时,从父进程将运行环境完整复制一遍。一个进程在前一个任务中对运行环境的改变,会原样体现在下一个任务的运行环境里。(即接受新任务的时候会直接继续使用上一个任务遗留下的运行环境)

from multiprocessing import Pool
import os, time

all_tasks_on_this_son_process = []

def init(init_name):
    global all_tasks_on_this_son_process
    all_tasks_on_this_son_process += [init_name]

def task(duration, base_time, task_name):
    global all_tasks_on_this_son_process
    pid = os.getpid()
    print(f'son process id {pid} starts working on {task_name} at {"%.6f" % (time.perf_counter()-base_time)}s with parameter {duration}, this process already executed',all_tasks_on_this_son_process)
    time.sleep(duration)
    print(f'son process id {pid} ends working on {task_name} at {"%.6f" % (time.perf_counter()-base_time)}s')
    all_tasks_on_this_son_process += [task_name]

if __name__ == '__main__':
    pid = os.getpid()
    base_time = time.perf_counter()
    print(f'main process id {pid} starts at {"%.6f" % (time.perf_counter()-base_time)}s')
    pool = Pool(3, initializer=init, initargs=('init',)) # look here
    print('start assigning tasks')
    for i in range(4):
        pool.apply_async(task, args=(1, base_time, "TaskNo."+str(i+1)))
    pool.close()
    print('all tasks assigned; wait for son processes to finish')
    pool.join()
    print(f'all tasks finished at {"%.6f" % (time.perf_counter()-base_time)}s')
    print(f'main process id {pid} ends at {"%.6f" % (time.perf_counter()-base_time)}s')

输出(Win下与Linux下相似):

main process id 18416 starts at 0.000004s
start assigning tasks
all tasks assigned; wait for son processes to finish
son process id 10052 starts working on TaskNo.1 at 0.053483s with parameter 1, this process already executed ['init']
son process id 17548 starts working on TaskNo.2 at 0.040412s with parameter 1, this process already executed ['init']
son process id 10124 starts working on TaskNo.3 at 0.049992s with parameter 1, this process already executed ['init']
son process id 10124 ends working on TaskNo.3 at 1.054387s
son process id 17548 ends working on TaskNo.2 at 1.044956s
son process id 10052 ends working on TaskNo.1 at 1.062396s
son process id 10124 starts working on TaskNo.4 at 1.055888s with parameter 1, this process already executed ['init', 'TaskNo.3']      
son process id 10124 ends working on TaskNo.4 at 2.060094s
all tasks finished at 2.443017s
main process id 18416 ends at 2.444705s

在进程池中利用子进程的返回值

from multiprocessing import Pool
import time

def task(duration, base_time, task_name):
    time.sleep(duration)
    return f'{task_name} finished at {"%.6f" % (time.perf_counter()-base_time)}s'

if __name__ == '__main__':
    base_time = time.perf_counter()
    pool = Pool(2)
    return_values = []
    return_values.append(pool.apply(task, args=(1,base_time,'TaskNo.1_sync')))
    print('at time {}, r_v is {}'.format(time.perf_counter() - base_time, return_values))
    return_values.append(pool.apply_async(task, args=(2,base_time,'TaskNo.2_async')))
    print('at time {}, r_v is {}'.format(time.perf_counter() - base_time, return_values))
    pool.close()
    pool.join()
    print(f'all tasks finished at {"%.6f" % (time.perf_counter()-base_time)}s')
    assert return_values[1].ready() == True
    return_values[1] = return_values[1].get() # from ApplyResult to true return value
    print('results:', return_values)
at time 1.2109459, r_v is ['TaskNo.1_sync finished at 1.027223s']
at time 1.2124976, r_v is ['TaskNo.1_sync finished at 1.027223s', <multiprocessing.pool.ApplyResult object at 0x0000016D24D79AC0>]     
all tasks finished at 3.258190s
results: ['TaskNo.1_sync finished at 1.027223s', 'TaskNo.2_async finished at 3.041053s']

这里在pool.join()之后调用result.get(),所以可以立刻得到 子进程所执行的函数的返回值;如果在对应的子进程尚未return时就调用result.get(),则主进程会阻塞直到子进程返回,然后获取子进程所执行的函数的返回值。result.ready()返回一个bool,表示对应的子进程是否已经return。

此外,result.wait()会阻塞直到子进程返回,但不会获取返回值。

一个ApplyResult实例可以多次调用get(),即可以多次获取返回值。

进程间通讯

可以认为,任何一个被跨进程传送的对象,在传送过程中都会被深拷贝。
Pipe

from multiprocessing import Process, Pipe
import time

def send_through_pipe(conn, pipe_name, sender_name, content, base_time):
    print(sender_name, 'tries to send', content, 'through', pipe_name, 'at', '%.6f'%(time.perf_counter()-base_time))
    conn.send(content)
    print(sender_name, 'successfully finishes sending at', '%.6f'%(time.perf_counter()-base_time))
    
def receive_from_pipe(conn, pipe_name, receiver_name, base_time):
    print(receiver_name, 'tries to receive content from', pipe_name, 'at', '%.6f'%(time.perf_counter()-base_time))
    content = conn.recv()
    print(receiver_name, 'successfully receives', content, 'at', '%.6f'%(time.perf_counter()-base_time))
    return content

def task(conn, pipe_name, process_name, base_time):
    receive_from_pipe(conn, pipe_name, process_name, base_time)
    time.sleep(1)
    send_through_pipe(conn, pipe_name, process_name, 142857, base_time)

if __name__ == '__main__':
    base_time = time.perf_counter()
    conn_A, conn_B = Pipe() # two endpoints of the pipe
    p1 = Process(target=task, args=(conn_B,'pipe','son',base_time))
    p1.start()
    
    time.sleep(1)
    send_through_pipe(conn_A, 'pipe', 'main', ['hello','hello','hi'], base_time) # any object can be sent
    receive_from_pipe(conn_A, 'pipe', 'main', base_time)
    p1.join()
son tries to receive content from pipe at 0.036439
main tries to send ['hello', 'hello', 'hi'] through pipe at 1.035570
main successfully finishes sending at 1.037174
main tries to receive content from pipe at 1.037318
son successfully receives ['hello', 'hello', 'hi'] at 1.037794
son tries to send 142857 through pipe at 2.039058
son successfully finishes sending at 2.040158
main successfully receives 142857 at 2.040441

另外,还可以用conn.poll()(返回Bool类型)来获知conn中是否有对面发来的未读信息。

from multiprocessing import Process, Pipe
import time

def send_through_pipe(conn, pipe_name, sender_name, content, base_time):
    print(sender_name, 'tries to send', content, 'through', pipe_name, 'at', '%.6f'%(time.perf_counter()-base_time))
    conn.send(content)
    print(sender_name, 'successfully finishes sending at', '%.6f'%(time.perf_counter()-base_time))
    
def receive_from_pipe(conn, pipe_name, receiver_name, base_time):
    print(receiver_name, 'tries to receive content from', pipe_name, 'at', '%.6f'%(time.perf_counter()-base_time))
    content = conn.recv()
    print(receiver_name, 'successfully receives', content, 'at', '%.6f'%(time.perf_counter()-base_time))
    return content

def task1(conn, pipe_name, process_name, base_time):
    receive_from_pipe(conn, pipe_name, process_name, base_time)
    time.sleep(1)
    send_through_pipe(conn, pipe_name, process_name, 'greetings from ' + process_name, base_time)

def task2(conn, pipe_name, process_name, base_time):
    time.sleep(1)
    send_through_pipe(conn, pipe_name, process_name, 'greetings from ' + process_name, base_time)
    time.sleep(2)
    receive_from_pipe(conn, pipe_name, process_name, base_time)

if __name__ == '__main__':
    base_time = time.perf_counter()
    conn_A, conn_B = Pipe()
    p1 = Process(target=task1, args=(conn_A,'pipe','son1',base_time))
    p2 = Process(target=task2, args=(conn_B,'pipe','son2',base_time))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
son1 tries to receive content from pipe at 0.033372
son2 tries to send greetings from son2 through pipe at 1.058998
son2 successfully finishes sending at 1.060660
son1 successfully receives greetings from son2 at 1.061171
son1 tries to send greetings from son1 through pipe at 2.062389
son1 successfully finishes sending at 2.063290
son2 tries to receive content from pipe at 3.061378
son2 successfully receives greetings from son1 at 3.061843

由此可见:

  • Pipe可以暂存数据,而且其暂存的数据符合FIFO规则。
    但是,Pipe用于暂存数据的区域大小比较有限(具体大小随OS而定),如果这个区域满了,send()阻塞,直到对面用recv()腾出位置为止。

  • Pipe的两个端点可以分配给任意两个进程。

  1. 不建议把同一个端点分配给多个进程,这可能会带来风险;如果确实需要的话,请使用Queue。

Queue本质上是一个能够跨进程运行的队列。

Queue的操作的时间开销约为Pipe中对应操作的两倍。

from multiprocessing import Process, Queue
import time

def put_into_queue(q, queue_name, putter_name, content, base_time):
    print(putter_name, 'tries to put', content, 'into', queue_name, 'at', '%.6f'%(time.perf_counter()-base_time))
    q.put(content)
    print(putter_name, 'successfully finishes putting at', '%.6f'%(time.perf_counter()-base_time))
    
def get_from_queue(q, queue_name, getter_name, base_time):
    print(getter_name, 'tries to receive content from', queue_name, 'at', '%.6f'%(time.perf_counter()-base_time))
    content = q.get()
    print(getter_name, 'successfully gets', content, 'at', '%.6f'%(time.perf_counter()-base_time))
    return content

def task1(q, delay, queue_name, process_name, base_time):
    time.sleep(delay)
    put_into_queue(q, queue_name, process_name, 'christmas card from ' + process_name, base_time)
    time.sleep(5)
    get_from_queue(q, queue_name, process_name, base_time)

def task2(q, delay, queue_name, process_name, base_time):
    time.sleep(delay)
    get_from_queue(q, queue_name, process_name, base_time)
    time.sleep(5)
    put_into_queue(q, queue_name, process_name, 'christmas card from ' + process_name, base_time)

if __name__ == '__main__':
    base_time = time.perf_counter()
    q = Queue()
    put_and_get_1 = Process(target=task1, args=(q,0,'queue','putAndGet_No.1',base_time))
    get_and_put_1 = Process(target=task2, args=(q,1,'queue','getAndPut_No.1',base_time))
    get_and_put_2 = Process(target=task2, args=(q,2,'queue','getAndPut_No.2',base_time))
    put_and_get_1.start()
    get_and_put_1.start()
    get_and_put_2.start()
    put_and_get_1.join()
    get_and_put_1.join()
    get_and_put_2.join()
putAndGet_No.1 tries to put christmas card from putAndGet_No.1 into queue at 0.077883
putAndGet_No.1 successfully finishes putting at 0.079291
getAndPut_No.1 tries to receive content from queue at 1.104196
getAndPut_No.1 successfully gets christmas card from putAndGet_No.1 at 1.105489
getAndPut_No.2 tries to receive content from queue at 2.126434
putAndGet_No.1 tries to receive content from queue at 5.081044
getAndPut_No.1 tries to put christmas card from getAndPut_No.1 into queue at 6.106381
getAndPut_No.1 successfully finishes putting at 6.107820
getAndPut_No.2 successfully gets christmas card from getAndPut_No.1 at 6.108565
getAndPut_No.2 tries to put christmas card from getAndPut_No.2 into queue at 11.109579
getAndPut_No.2 successfully finishes putting at 11.112493
putAndGet_No.1 successfully gets christmas card from getAndPut_No.2 at 11.113546

另外,如果Queue的大小实在过大以至于达到了某个上限,则put()操作也会被阻塞。不过应该很难把大小弄到那么大。

多线程
基本语法和多进程很相似,但机制上有重要的不同。由于全局解释器锁的存在,Python多线程并不实用,这里仅作简单介绍。

从下图中可以看到,多线程的基本代码和多进程完全一致。下图中的代码在CPython解释器中会运行大约3s。

另外,多线程中其实不需要这个if __name__ == '__main__':的判断。
在这里插入图片描述
多线程的变量机制

import threading

lock_n = threading.Lock()
n = 0

def inc_n(m):
    global n
    lock_n.acquire(blocking=True)
    n += m
    lock_n.release()

threads = [threading.Thread(target=inc_n, args=(i,)) for i in range(1,11)]
[t.start() for t in threads]
[t.join() for t in threads]

print(n)
55
  • 由上可见,不同的线程之间共享运行环境(比如上面的变量n)。

  • lock.acquire(blocking=True) 会一直阻塞直到锁空出来为止;一旦空出来就会把它锁上。

并发处理:协程

不同的过程在同一个线程内交替执行。每个协程在运行时独占资源,一段运行结束后自阻塞,等待着被外部(如main函数)控制它的代码唤醒。

相比多线程的优点:轻量级(在解释器层面实现,不需要内核来做切换)、数量不限。

和多线程一样,不同协程之间共用运行环境。

用简单的生成器实现协程

def sum(init):
    s = init
    while True:
        delta = yield s # output s, and then input delta
        s += delta
        
g = sum(0)
print(next(g)) # run entil receiving the first output
print(g.send(1)) # send the first input, and then get the second output
print(g.send(2)) # send the second input, and then get the third output
0
1
3

上例中只是演示了生成器的自阻塞,以及生成器与其调用者之间的交互。

更进一步,还可以定义多个生成器执行不同的过程,并在main函数里进行对它们的调度(比如实现一个任务队列),从而实现协程。

用回调函数(callback)将普通函数变为协程

def calc(n,callback):
    r = 0
    for i in range(n):
        r += i
        callback()

def pause():
    print('pause')
    yield # just pause, do not return anything

g = calc(10,pause)

用async/await实现协程
相比生成器实现的优点:可以在等待IO/等待网络通信等情况下时阻塞当前协程执行其他协程(而且不会中断等待IO/通信)以节省时间(而只用生成器则无法做到);使用更灵活、方便。

  • 多线程其实也有前一个优点。所以CPython下的多线程也并不是毫无用处,但它的用处是协程用处的子集。
  • 一个注意事项:若想通过协程加速IO,必须使用python中专门的异步IO库才行。

基础使用

import time

start = time.perf_counter()

def sayhi(delay):
    time.sleep(delay)
    print(f'hi! at {time.perf_counter() - start}')

def main():
    sayhi(1)
    sayhi(2)

main()
hi! at 1.0040732999914326
hi! at 3.015253899997333
import time
import asyncio

start = time.perf_counter()

async def sayhi(delay):
    await asyncio.sleep(delay)
    print(f'hi! at {time.perf_counter() - start}')

async def main():
    sayhi1 = asyncio.create_task(sayhi(1))
    sayhi2 = asyncio.create_task(sayhi(2))
    await sayhi1
    await sayhi2

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
hi! at 1.0037910000100965
hi! at 2.0026504999987083

上面的程序中:

  • async声明当前函数是一个协程。这一声明使得函数体内可以使用create_task和await,也使得该函数本身可以被create_task和await。

一旦一个函数f被声明为协程,f()做的事就不再是运行f,而只是创建一个协程对象并返回之(这个对象并不会自动被运行)。需要使用asyncio中的相关工具来运行这个对象。

  • run(main())表示开始执行协程main() 。要求main()必须是“主协程”,即它是整个程序中所有协程的入口(类似主函数)。一个程序中run(main()) 只应被调用一次,且在main() 之外不应有任何协程被调用。
  1. run(main())协程的并发特性只有在main()内部才会显现,从外部来看这就是一个普普通通的黑箱调用。
  2. run()的作用是启动 运行协程所需的环境(并在main()完成后将其关闭)。但在Python开始运行就已经自动帮你启动好了,所以可以直接用await(而且也不必把所有协程都放在一个主协程中,而可以散布在程序各处)。
  • create_task(sayhi(1))表示为协程sayhi(1)在某个“任务池”中创建一个任务,并且开始执行该任务。返回值是这个任务的handle,或者说“遥控器”。
  1. 任务池中的任务会并发地执行。任务在何时可以中断并切换到别的任务,这一点由await指定。

-await sayhi1有两重含义:

  1. 阻塞当前协程(该语句所在的协程,这里是main())的执行,直到任务sayhi1完成。(类似Process.join())
  2. 告诉解释器,现在当前协程(该语句所在的协程)开始阻塞,你可以切换协程了。

如果这里await的不是sayhi1而是,比如说,一个接受http请求的操作,那么在解释器切换协程后不会影响对这个请求的等待。这就是在这里插入代码片的强大之处。

这一点在await asyncio.sleep(delay)就有体现。asyncio.sleep()就具有“切换协程不影响等待”的特性。

  • 关于await的几件事:

1.await的可以不是已创建的任务而是一个协程对象(比如await sayhi(1)),此时不会将其加入任务池,而会直接开始执行(当然,也可能刚开始执行就被切换到别的协程,因为用了await),并一直阻塞直到完成。这会导致sayhi(1)无法作为一个任务、与其他任务平等地参与并发,但是它仍然可以随着父协程(这里是main())的中断和恢复而间接地参与并发。

  1. 能够被await的不只有协程对象和任务handle,还有任何awaitable object,即任何实现了__await__方法(从而告诉了解释器如何在自己刚开始执行时就阻塞并切换协程,且不影响内部可能在进行的等待和其他操作)的对象。

  2. await的对象只可能在刚开始执行时立刻阻塞并切换协程。执行过程中其他可以阻塞的位置,是由这个对象内部使用的其他await语句指定的,而不是调用这个对象的那条await语句。

import time
import asyncio

start = time.perf_counter()

async def sayhi(delay):
    await asyncio.sleep(delay)
    print(f'hi! at {time.perf_counter() - start}')

async def main():
    await sayhi(1)
    await sayhi(2)

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
hi! at 1.0072715999995125
hi! at 3.0168006000021705
wait_for()

把await A(A为任意)改成await asyncio.wait_for(A,timeout),就可以给await操作加上timeout秒的时限,一旦await了这么多秒还没有结束,就会中断A的执行并抛出asyncio.TimeoutError。

不用关心wait_for什么,你只需要记住await asyncio.wait_for(A,timeout)这个句子就行。可以认为这个句子和await A在(除了timeout以外的)其他方面上没有区别。下面是例子。

import time
import asyncio


async def eternity():
    await asyncio.sleep(3600)
    print('yay!')

async def main():
    try:
        await asyncio.wait_for(eternity(), timeout=1.0)
    except asyncio.TimeoutError:
        print('timeout!')

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
timeout!
import time
import asyncio

start = time.perf_counter()

async def sayhi(delay):
    await asyncio.sleep(delay)
    print(f'hi! at {time.perf_counter() - start}')

async def main():
    sayhi1 = asyncio.create_task(sayhi(1))
    sayhi2 = asyncio.create_task(sayhi(2))
    await asyncio.wait_for(sayhi1,1.05)
    await asyncio.wait_for(sayhi2,1.05)

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
hi! at 1.0181081000046106
hi! at 2.0045300999918254
import time
import asyncio

start = time.perf_counter()

async def sayhi(delay):
    await asyncio.sleep(delay)
    print(f'hi! at {time.perf_counter() - start}')

async def main():
    sayhi1 = asyncio.create_task(sayhi(1))
    sayhi2 = asyncio.create_task(sayhi(2))
    await asyncio.wait_for(sayhi1,0.95)
    await asyncio.wait_for(sayhi2,1.05)

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
---------------------------------------------------------------------------

TimeoutError                              Traceback (most recent call last)

<ipython-input-89-7f639d54114e> in <module>
     15 
     16 # asyncio.run(main()) # use outside IPython
---> 17 await main() # use inside IPython


<ipython-input-89-7f639d54114e> in main()
     11     sayhi1 = asyncio.create_task(sayhi(1))
     12     sayhi2 = asyncio.create_task(sayhi(2))
---> 13     await asyncio.wait_for(sayhi1,0.95)
     14     await asyncio.wait_for(sayhi2,1.05)
     15 


~\anaconda3\lib\asyncio\tasks.py in wait_for(fut, timeout, loop)
    488             # See https://bugs.python.org/issue32751
    489             await _cancel_and_wait(fut, loop=loop)
--> 490             raise exceptions.TimeoutError()
    491     finally:
    492         timeout_handle.cancel()


TimeoutError: 


hi! at 2.0194762000028277

另外,注意到即使协程sayhi1抛出了异常,父协程main()仍然能够继续执行sayhi2。可见不同协程间是有一定的独立性的。

实现生产者-消费者协程
为此需要使用asyncio.Queue 。它相比普通的队列的区别是,其put/get操作会在无法执行时阻塞(这一点和multiprocessing.Queue很像),而且这些操作都是协程(注意到,这使得你调用它们时只会返回协程对象而不会实际执行),可以await。

import time
import asyncio

start = time.perf_counter()

async def producer(q):
    for i in range(5):
        await asyncio.sleep(1) # producing takes 1 sec
        await q.put(i) # will wait if q is full
        print(f'put {i} at {time.perf_counter() - start}')
    
    await q.join() # will wait until all objects produced are **taken out** and **consumed**.

async def consumer(q):
    for i in range(5):
        item = await q.get() # will wait if q is empty. BTW we see that "await XXX" is an expression not a command.
        print(f'get {item} at {time.perf_counter() - start}')
        await asyncio.sleep(1) # consuming takes 1 sec
        q.task_done() # tells the queue that [the object just taken out] has been consumed. just taking out is not enough!
        print(f'consumed {item} at {time.perf_counter() - start}')
    
async def main():
    q = asyncio.Queue()
    P = asyncio.create_task(producer(q))
    C = asyncio.create_task(consumer(q))
    await P
    await C
    print(f'done at {time.perf_counter() - start}')

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
put 0 at 1.0108397000003606
get 0 at 1.0112231999955839
put 1 at 2.017216499996721
consumed 0 at 2.0176210000063293
get 1 at 2.0177472999930615
put 2 at 3.0279211000015493
consumed 1 at 3.0283254999958444
get 2 at 3.028457599997637
put 3 at 4.039952199993422
consumed 2 at 4.041183299996192
get 3 at 4.041302300000098
put 4 at 5.0465819999953965
consumed 3 at 5.04690839999239
get 4 at 5.047016099997563
consumed 4 at 6.047789799995371
done at 6.048323099996196
import time
import asyncio

start = time.perf_counter()

async def sleep_and_put(q):
    await asyncio.sleep(1)
    await q.put(1)

async def main():
    q = asyncio.Queue()
    C = asyncio.create_task(q.get())
    P = asyncio.create_task(sleep_and_put(q))
    await C
    await P
    print(f'finished at {time.perf_counter() - start}')

# asyncio.run(main()) # use outside IPython
await main() # use inside IPython
finished at 1.01112650000141

由上例可见,Queue.get()(其实Queue.put()等其他方法也一样)是一个协程,因此也可以给它创建任务以进行并发。

ur3手眼标定+realsenseL515 知乎 csdn 阅读详情

相关推荐

《湖科大教书匠计算机网络》学习笔记总目录+思维导图

本篇文章是对《湖科大教书匠计算机网络》所有知识点的笔记总结归档和计算机网络的思维导图

weixin_73077810的博客 8225

python 多进程异常处理

前言 最近项目用到了Python作为网站的前端,使用的框架是基于线程池的Cherrypy,但是前端依然有一些比较‘重’的模块。由于python的多线程无法很好的利用多核的性质,所以觉得把这些比较‘重’的功能用多进程进行管理。 Python 多进程编程 Python的多进程编程主要依赖multiprocessing库,父进程的参数直接拷贝给子进程,因为Linux进程的copy on write...

whatday的专栏 4468

解决Calibre中license的错误

本次解决了Calibre在Cadence 617 virtuoso版本中运行不了的错误,在遇到问题的时候,我也跟你们一样,首先是上网查询为什么运行不了呢?明明我安装了之后里面有Calibre的,我当时也是这样的疑惑,一点Calibre运行LVS的时候,一片空白,然后就出现了license的错误,后来点点点,看看看,搜搜搜,最终解决了这个问题。虚拟机是VMware16版本,cadence617,Calibre是2015版本。旨在记录遇到的实际问题。

yytu231的博客 1万+

python多进程的坑,python在windows上使用多进程的坑

一,python的main函数。一般的语言都是从main函数开始的。python有点不同,Python使用缩进对齐组织代码的执行,所有没有缩进的代码(非函数定义和类定义),都会在载入时自动执行,这些代码,可以认为是Python的main函数。python的主函数一般都写成if __name__ == "__main__":当这个模块是主调用的和被调用的时候,__name__的值是不一样的,当这个模...

weixin_42101641的博客 1397

在运行opencv时出现的两个问题:AttributeError: module ‘cv2‘ has no attribute ‘gapi_wip_gst_GStreamerPipeline‘

解决了opencv的两个问题:由问题一引出 AttributeError: module 'cv2' has no attribute 'gapi_wip_gst_GStreamerPipeline' cv2.error:OpenCV(4.1.2)C:\projects\opencvpython\opencv\modules\highgui\src\window.cpp:717:error: (-2:Unspecified error) The function is not implemented...

hu_yinghui的博客 3274

【Tessent Shell Users Manual】【Ch3】Design Introspection and Editing(未完待续)

Tessent Shell 提供了一系列用于检查(examining/introspecting)和编辑design的命令。

Tranquil_ovo的博客 1731

Python并发编程之多进程(孤儿,僵尸,守护进程)

七.孤儿进程 1.什么是孤儿进程 当一个父进程创建了多个子进程, 子进程再创建子子进程等等 父进程因正常运行完毕或其他情况被干掉的时候, 它的子进程就变成了孤儿进程 为了避免孤儿进程完成任务后没有父亲通知操作系统回收资源 于是 PID 为 "1"的顶级进程 systemd 就接手了这个孤儿进程 systemd 相当于一个孤儿院, 但凡是孤儿进程都会成为它的子进程 2.孤儿进程演示 先在一个虚拟终端里开启一个 Bash 进程,把他当做父进程 紧接着开启一个 “sleep 1000 &” 进程,

Shawn派大星 862

《手把手教你YOLOv9实战》,改进专栏目录

为了提供友好的操作效果,本专栏将会手把手搭建可视化界面,我将用PyQt5 搭建一个可视化界面,可视化界面能够实现基本的图像加载与检测功能,如支持视频、摄像头,还支持更换不同的 YOLOv9 模型,并适应不同领域(如口罩检测系统、人脸检测系统、工业缺陷检测系统等),界面灵活、功能强大。通过YOLOv9加以改进设计,形成新的算法框架,一起水科研和论文,专栏会一直持续更新中,本专栏适合目标检测、分割、分类。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 2152

1 - 进程 - Windows 10 - Python - multiprocessing - 简单多进程切换、进程传参、异步进程、守护进程(进程睡眠/堵塞和线程堵塞的区别)、主/子进程区分

目录一、单进程二、多进程的实现三、多进程传参四、获取多进程 id 编号五、主进程会等待所有的子进程执行结束再结束。六、设置守护进程,当主进程结束时,子进程也不再继续执行,直接结束。七、关于多进程必须加上 `if __name__ == "__main__"` 的理由(进程区分):八、Jupyter Notebook 与 进程区分的关联九、关于多进程与GPU的关系 一、单进程 一般来说我们运行可执行文件,如脚本文件等,就相当于是在运行一个进程,系统会自动分配资源给这个文件运行,而这个进程就是父进程,或者说是

Muxiu 2870

PCL Fast Global Registration 快速全局配准(源码版)

Fast Global Registration(FGR)是一种经典的点云粗配准算法,已集成到Open3D和CloudCompare等软件中。本文介绍了该算法的源码实现及优化方法,包括如何通过修改源码直接读取点云进行配准,而不依赖文件中读取特征描述子。文章还提供了关键参数设置(如最大迭代次数、匹配距离等)。算法实现简单高效,未来计划发布基于纯PCL的版本。源码和论文链接已附上,供开发者参考。

点云侠的博客 382

GraphRAG深度解析:架构设计与实战案例

生成模块结合检索到的图数据和用户输入的查询,生成高质量的文本输出。通过结合图数据库中的知识,模型可以更准确地回答用户的问题,提供更优质的客户服务。通过分析用户的行为和偏好,结合图数据库中的知识,模型可以为用户提供个性化的推荐。:通过图数据库存储和检索结构化的知识,为语言模型提供丰富的背景信息,提升生成内容的质量和准确性。:选择合适的图数据库,如Neo4j、OrientDB等,以支持高效的图数据存储和检索。:利用图数据库的高效检索能力,快速找到与用户查询最相关的图元素,提高系统的响应速度。

加入“Super Entity”,与全能开发团队共探AI智能体与数字人项目,开启前沿技术之旅。 1589

【python】python进程、线程、协程和什么时候使用

说明 进程:是操作系统进行资源分配的最小单元,资源包括CPU、内存、磁盘等IO设备等等 线程:是CPU调度的基本单位。 进程:系统分配资源的载体,是程序运行的实例; 线程:程序执行的最小单元,是进程中的一个实体用来执行程序,一个进程中有多个线程。 为什么有人说 Python 多线程是鸡肋? 在我们常识中,多进程、多线程都是通过并发的方式充分利用硬件资源提高程序的运行效率,怎...

我的笔记本 9000

【动态导通电阻】GaN HEMT动态导通电阻的精确测量

2023年7月,瑞士洛桑联邦理工学院的Hongkeng Zhu和Elison Matioli在《IEEE Transactions on Power Electronics》期刊发表文章,提出了一种稳态测量方法,用于研究氮化镓(GaN)高电子迁移率晶体管(HEMT)的动态导通电阻(Ron)。实验结果显示,商业GaN器件在稳态下的动态Ron变化显著,稳定时间可达数分钟且与电压相关。传统的脉冲测量方法由于测试时间不足,可能导致结果不准确。该

荷塘阅色 1376

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

脑海中关于进程和线程的概念一直很模糊,什么时候该用多进程,什么时候该用多线程总是搞不清楚。同时python因为历史遗留问题存在GIL全局锁,就让人更加困惑。这一篇就完整整理一下python中进程和线程的概念和实现。 文章目录进程和线程GIL锁多进程 vs 多线程代码实现多进程进程池进程间通讯多线程线程池异步线程间通讯总结 进程和线程 进程(process)和线程(thread)的区别应该算是个老生常谈的话题。 这里引用知乎用户的一个高赞回答来深入浅出的解释一下 看了一遍排在前面的答案,类似”进程是资源分配的

T型人小付的博客 3万+

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Python之多进程与多线程

前言:为什么有人说 Python 的多线程是鸡肋,不是真正意义上的多线程? 看到这里,也许你会疑惑。这很正常,所以让我们带着问题来阅读本文章吧。问题: 1、Python多线程为什么耗时更长? 2、为什么在Python里面推荐使用多进程而不是多线程? 1 基础知识 现在的PC都是多核的,使用多线程能充分利用CPU来提供程序的执行效率。 1.1 线程 线程是一个基本的CPU执行单元。...

ZONGXP的博客 6万+

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

听说Python多线程和多进程有鸡肋?一起聊聊...

Python 的并发编程有多种实现方式,包括多线程、多进程和协程等。其中,多线程通常适用于 I/O 密集型的任务,但由于 GIL 的存在,不能真正发挥出多核处理器的性能;而多进程则可以真正发挥出多核处理器的性能,但进程之间的通信和数据共享比较麻烦,每个进程的启动和销毁也会涉及到一定的开销。协程则是一种轻量级的并发处理方式,适用于 I/O 密集型任务和部分计算密集型任务,可以通过 async/await 关键字和 asyncio 库来实现。

Python966的博客 2213

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

听说Python多线程和多进程有鸡肋?一起聊聊

在单线程模式下,循环完成后 counter 的值应该是 40000000,但是在多线程模式下,由于 GIL 的限制,多个线程并不能真正并行地执行代码,导致 counter 的最终值小于 40000000。在我的机器上运行这个代码示例,可以看到几乎同时访问 4 个 URL,并在几乎相同的时间内完成了任务,证明了 asyncio 库在 I/O 密集型任务中的性能优势。在我的机器上运行这个代码示例,可以看到 4 个线程几乎同时执行,并在几乎相同的时间内完成了任务,证明了多线程在 I/O 密集型任务中的性能优势。

2501_90433086的博客 1068

python多线程_鸡肋?Python的多线程与多进程

GIL 的存在一直是富有争议的,它导致 Python 程序无法真正利用现代操作系统的多进程特性。需要注意的是,对于 I/O 图形处理、NumPy 数学计算这样的耗时操作都发生在 GIL 之外,实际上基本不受影响,真正受影响的都是 Python 字节码的执行,GIL 会导致性能瓶颈的出现。总之,只有在使用纯 Python 做 CPU 密集的多线程运算时 GIL 会是问题。GIL是什么Python的代...

weixin_39947908的博客 281

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

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

weixin_30750335的博客 265

python 先知论坛_Python多线程鸡年不鸡肋

当初在刚学习python多线程时,上网搜索资料几乎都是一片倒的反应python没有真正意义上的多线程,python多线程就是鸡肋。当时不明所以,只是了解到python带有GIL解释器锁的概念,同一时刻只能有一个线程在运行,遇到IO操作才会释放切换。那么,python多线程是否真的很鸡肋呢?要解决这个疑惑,我想必须亲自动手测试。经过对比python与java的多线程测试,我发现python多线程的效...

weixin_33977645的博客 205

Python多进程、多线程、协程概念及用法详解

说明 进程:是操作系统进行资源分配的最小单元,资源包括CPU、内存、磁盘等IO设备等等 线程:是CPU调度的基本单位。 进程:系统分配资源的载体,是程序运行的实例; 线程:程序执行的最小单元,是进程中的一个实体用来执行程序,一个进程中有多个线程。 为什么有人说 Python 多线程是鸡肋? 在我们常识中,多进程、多线程都是通过并发的方式充分利用硬件资源提高程序的运行效率,怎么在 Python 中反而成了鸡肋? 因为 Python 中臭名昭著的 GIL。 那么 GIL 是什么?为什么会有.

qq_42420425的博客 494

python多线程没用_Python多线程鸡年不鸡肋

当初在刚学习python多线程时,上网搜索资料几乎都是一片倒的反应python没有真正意义上的多线程,python多线程就是鸡肋。当时不明所以,只是了解到python带有GIL解释器锁的概念,同一时刻只能有一个线程在运行,遇到IO操作才会释放切换。那么,python多线程是否真的很鸡肋呢?要解决这个疑惑,我想必须亲自动手测试。经过对比python与java的多线程测试,我发现python多线程的效...

weixin_39778214的博客 246

python 跑满cpu_鸡肋?Python的多线程与多进程

GIL 的存在一直是富有争议的,它导致 Python 程序无法真正利用现代操作系统的多进程特性。需要注意的是,对于 I/O 图形处理、NumPy 数学计算这样的耗时操作都发生在 GIL 之外,实际上基本不受影响,真正受影响的都是 Python 字节码的执行,GIL 会导致性能瓶颈的出现。总之,只有在使用纯 Python 做 CPU 密集的多线程运算时 GIL 会是问题。GIL是什么Python的代...

weixin_36028920的博客 1015

python gil协程_Python中协程、多线程、多进程、GIL锁

概念:协程:协程是轻量级的线程,又称微线程,协程由程序自身控制,在一个线程里执行,没有线程切换的开销,有极高的执行效率。线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程中的实际运作单位,一个进程中可以有多个线程。进程:进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础,是线程的容器。(是指令、数据及其组织形式的描述,进程是程序的实体。)并发:当有多个线程在操作时,...

weixin_39980575的博客 163

python多进程和多线程一起使用_Python 多进程与多线程

前言:为什么有人说 Python 的多线程是鸡肋,不是真正意义上的多线程?看到这里,也许你会疑惑。这很正常,所以让我们带着问题来阅读本文章吧。问题:1、Python 多线程为什么耗时更长?2、为什么在 Python 里面推荐使用多进程而不是多线程?1 基础知识现在的 PC 都是多核的,使用多线程能充分利用 CPU 来提供程序的执行效率。1.1 线程线程是一个基本的 CPU 执行单元。它必须依托于进...

weixin_39665847的博客 1211

c语言多进程和多线程得区别,多线程和多进程不可能是鸡肋!只因你还不会!最全的进阶资料!...

原标题:多线程和多进程不可能是鸡肋!只因你还不会!最全的进阶资料!不管你是大牛还是萌新,学好Python这些资料是必须用得到的!你不会这些比较基础的知识你拿什么东西来做项目呢?特别是c语言对底层知识掌握更加的严格!今天给大家打来比较基础的干货合集,衷心的希望能帮助到大家学好Python!multiprocessing模块由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地...

weixin_35607472的博客 565
上一篇: Python的10个编程技巧,你不一定都知道!
下一篇: 递归解法:让程序员的代码更优雅,正经人谁用递归呀!
克金森沐沐
博客等级 码龄6年 694粉丝 64原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值