进程,多线程,线程同步
这是一篇根据自己查阅资料,基于自己理解写的关于进程,线程的文章。文章结构也是我思考方式的一个流程,如果有错误的地方,感谢指出。
1. 进程和线程
定义上说,进程是资源分配(CPU和内存)分配的基本单位,线程是程序执行的最小单元。根据我自己的理解,一个py文件就是一个进程,run这个py文件后,就产生了一个线程,称为主线程。也就是说,一个进程一定包含一个主线程。
资料上说,一个进程可以包含多个线程,这些线程是并发的,但在只有一个CPU的情况下,并发并不是严格意义上的同时执行,而是轮流做,比如线程1做0.01s,线程2再0.01s,线程1再做0.02s,从人的观测角度来说,会觉得线程1和2是同时执行的,实际上不是这样。
2. 为什么会有多线程
当系统中有一个很长的任务时,别的任务需要等它做完了才能开始做。假设有任务1:将一个word文件存储成pdf,这个过程需要10s;任务2: 在微信上的对话框上输入:你好,这个过程需要2s。如果等任务1做完,再做任务2,从用户的角度来说,在键盘上打了"你好"以后,需要等待10s才有响应,看起来就像是电脑死机了,这样的体验是很差的。
由此可以总结出多线程的目的1:为了让另一个任务也早点开始,早点响应。
如果任务1做1s,任务2做1s,任务1再做1s…这样轮流着来,这样任务2的响应就很快,但完成任务1和任务2的总时间是一样的。
2.1 那有没有对任务1和任务2使用多线程(轮流做)的时间,比任务1和任务2按顺序做的时间短的情况呢?
有的。
-
当任务1或任务2中有time.sleep()这样的代码时,系统是不会调用CPU的,也就是不会使用到资源,那么CPU就可以去忙下一个任务啦,也就是一遍让任务1等,一遍做任务2,总时间就会减少。
-
有多个CPU,就可以实现真正的同时处理多个任务。
3. 根据上面对多线程的解释,有疑惑的点吗?
有的(自问自答)。
3.1 前面提到,任务1做1s,任务2做1s,再做任务1时,怎么保证是从上次中断的地方继续做的呢?
需要记录中断的指令 -> 指令指针
记录内存的位置 -> 堆栈指针寄存器
3.2 如果两个线程都要对同一个变量进行操作。假设线程1是从某人的账户里取钱,线程2是向某人的账户里存钱,那操作不就乱套了吗?
是的,这就需要线程同步来解决这种冲突。
当线程想要对同一个共享变量进行操作时,必须先申请一把锁,申请到了才能操作共享变量,操作完后再释放锁。在操作过程中,别的线程会发现该变量已经上锁,就只能等待锁释放后,抢锁,抢到了才能操作该共享变量。
举一个例子,你和另一个人都想上厕所,但是厕所有门没锁,那可能会出现一种情况:正在上厕所的你,门被别人打开,是个想要和你一起上厕所的变态。但如果厕所有门有锁,那另一个人必须得等到你上完厕所,开锁出来了,才可以进去使用这个厕所,使用前也要锁上门,防止别人进来。
不知道理解的对不对,感觉线程同步感觉回到了最初的起点,所有的线程必须都按顺序执行,那就相当于没有多线程。但也许在掌握了线程的操作以后,也许就能实现线程1和线程2并行,但同时又和线程3同步的高端操作了吧。
4. Python中多线程的实现
是根据菜鸟的这个指南学习的。
python中多线程的实现是通过threading这个模块,它提供的方法有:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- isAlive(): 返回线程是否活动的。
- getName(): 返回线程名。
- setName(): 设置线程名。
- join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
这个 j o i n ( ) join() join() 的功能一开始没有太理解,搜索了一些别的资料后理解如下:
当一个进程启动时,会默认产生一个主线程,我们使用Threading模块创建的都是子线程。默认情况下,主线程在执行完自己的任务后,就会退出,此时子线程会继续执行自己的任务。也就是说,主线程的结束并不会影响子线程。
但有的时候我们会将创建的子线程 t t t 设为守护线程,具体方法是:
t.setDaemon(True)
t.start()
(顺序不能反)
守护线程:一种特殊子线程,当主线程及所有非守护线程(即普通子线程)结束时,守护线程立刻结束,不管该做的指令有没有做完。
但如果希望在所有子线程结束前,主线程一直挂起,那么可以就使用 t . j o i n ( ) t.join() t.join() 来达到该目的。也就是主线程会一直等待调用过 j o i n ( ) join() join() 函数的子线程都结束后,再结束。