线程锁
线程锁可以将临界区内的代码锁住,在同一时刻下,只有获得锁的线程可以进入,至于究竟哪个线程可以获得这把锁,则是由操作系统调度,或者是两个线程之间进行竞争,谁能先接触到锁,谁就能获得这把锁。
使用锁,来保证在检查tasks中是否有元素时,tasks不能被修改。将上面的pop函数改写一下
lock = threading.Lock() def pop(): global tasks while tasks: time.sleep(2) # 获得锁 lock.acquire() if tasks: print(tasks.pop()) else: break # 释放锁 lock.release()
通过使用线程锁将检查tasks和tasks.pop()两个动作锁起来,确保只有一个线程可以运行这两行代码,从而保证检查结果和检查后的结果一致。使用线程锁是为了避免资源竞争,但是如果锁使用的不合适,会让你的多线程程序退化为单线程程序。
如果我们将上面的pop函数改为下面这种,使用锁将整段代码全部锁住,那么这段代码和直接使用一个for循环相比速度差别不大,甚至会变得更慢(存在线程调度)。
def pop(): global tasks lock.acquire() while tasks: time.sleep(2) if tasks: print(tasks.pop()) else: break lock.release()
所以,线程锁使用一般在一些不是很耗时,但是会存在资源竞争的地方,程序中的一些耗时操作一般不放入临界区内。
进程
在python中多进程是没有GIL锁的,也就是说,多进程可以同时使用多个CPU核。说的形象一定就是,使用多线程,你的CPU使用率最多能到30%,而多进程可以达到90%以上。
进程的创建
进程与线程的创建一致,只是不再是使用Thread
而是使用Process
,创建方法与线程一样,也可以通过三种方式进行创建,这里请读者自行实现。
import multiprocessing # target即为进程内部要运行的函数 # args为函数所需要的参数,要以tuple类型传入 thread = multiprocessing.Process(target=compress, args=(picture_id, ))
进程的启动方式
与线程不同,进程的实现更依赖于操作系统,python中进程有三种启动方式
- spawn(unix和windows系统都可以使用)
- fork(只有unix系统可以使用)
- forkserver(只有unix系统可以使用)
三种启动方式,其中fork
的效率最高,spawn
的效率最低。使用set_start_method
方法可以设置特定的启动方法,注意,这个方法只能被调用一次,一般会放在if __name__ == '__main__':
下面。这里分析spawn
和fork
两种启动方式的差别。
spawn
spawn的启动方式是通过将一份代码使用python解释器运行多次,其中主进程的__name__
为__main__
,而通过主进程启动子进程的__name__
为__mp_main__
from multiprocessing import ( Process, set_start_method ) import os print('Hello, world. __name__: %s' % __name__) def func(): print(os.getpid()) if __name__ == '__main__': set_start_method('spawn') p1 = Process(target=func) p2 = Process(target=func) p1.start() p2.start() p1.join() p2.join()
你会发现,hello,world被打印了三次,也印证了spawn方式是通过将同一份代码执行了三次。分别是主进程一次,和两个子进程各执行了一次。
fork
fork则是使用的系统调用,在调用fork的位置直接获得子进程和父进程
from multiprocessing import ( Process, set_start_method ) import os print('Hello, world. __name__: %s' % __name__) def func(): print(os.getpid()) if __name__ == '__main__': set_start_method('fork') p1 = Process(target=func) p2 = Process(target=func) p1.start() p2.start() p1.join() p2.join()
fork方式只打印了一次hello,world,即在主进程启动的时候,在主进程创建子进程的时候,没有将整个代码重新运行一遍,而是只在创建时,直接获得了子进程。熟悉linux系统中fork
的同学应该会比较熟悉这种方式。所以同样的一份代码,放到windows系统是可以运行,但是放到Linux系统或者是苹果的macos系统的上就不能运行了,原因就出在了进程的启动方式不同。在编写多进程代码时,这一点要格外注意。