Python thread and process
主要围绕遇到的python threading包的一些知识点
OMP_NUM_THREADS
Thread.join()
参考------------------->Python thread join()
主要作用就是让线程有优先执行权,即一定要执行完才进行下面指令,类似于同步的概念
import threading
def action(*add):
for arc in add:
#调用 getName() 方法获取当前执行该程序的线程名
print(threading.current_thread().getName() +" "+ arc)
#定义为线程方法传入的参数
my_tuple = ("http://c.biancheng.net/python/",\
"http://c.biancheng.net/shell/",\
"http://c.biancheng.net/java/")
#创建线程
thread = threading.Thread(target = action,args =my_tuple)
#启动线程
thread.start()
#指定 thread 线程优先执行完毕
# thread.join()
#主线程执行如下语句
for i in range(5):
print(threading.current_thread().getName())
此时结果为
Thread-1 http://c.biancheng.net/python/
MainThread
Thread-1 http://c.biancheng.net/shell/
MainThread
Thread-1 http://c.biancheng.net/java/
MainThread
MainThread
MainThread
如果将**thread.join()**注释去掉,则输出为
Thread-1 http://c.biancheng.net/python/
Thread-1 http://c.biancheng.net/shell/
Thread-1 http://c.biancheng.net/java/
MainThread
MainThread
MainThread
MainThread
MainThread
setDaemon
守护进程的概念,正好最近听龙少说linux也有守护进程的概念,就像在命令行shell命令如果是父进程则在命令行中的fork的其他进程为子进程,父进程结束是不是子进程也会结束?
在python setDaemon就有这样的效果
具体参考--------------------->Python为什么要有setDeamon这里面讲的比较深入,在python中表现得最明显的就是下面例子
import time
import threading
def test():
while True:
print threading.currentThread()
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=test)
t2 = threading.Thread(target=test)
t1.start()
t2.start()
输出结果为
^C<Thread(Thread-2, started 123145414086656)>
<Thread(Thread-1, started 123145409880064)>
CCCCCC<Thread(Thread-2, started 123145414086656)> # ctrl-c 多次都无法中断
<Thread(Thread-1, started 123145409880064)>
^C<Thread(Thread-1, started 123145409880064)>
<Thread(Thread-2, started 123145414086656)>
<Thread(Thread-1, started 123145409880064)>
<Thread(Thread-2, started 123145414086656)>
<Thread(Thread-2, started 123145414086656)><Thread(Thread-1, started 123145409880064)>
…(两个线程竞相打印)
这是作者的结果,但是我使用的python3可能有些改动,使用ctrl-c直接退出了,但是这个死循环确实是在一直存在。
如果将t1和t2进程都设置成守护进程,则结果为
python2.7 1.py
<Thread(Thread-1, started daemon 123145439883264)>
<Thread(Thread-2, started daemon 123145444089856)>
(直接退出了)
注意设置守护进程时要在线程启动之前,不然会报错
Traceback (most recent call last):
File “thread_join.py”, line 39, in
t1.setDaemon(True)
File “/home/amax/anaconda3/envs/torch/lib/python3.8/threading.py”, line 1122, in setDaemon
self.daemon = daemonic
File “/home/amax/anaconda3/envs/torch/lib/python3.8/threading.py”, line 1115, in daemon
raise RuntimeError(“cannot set daemon status of active thread”)
RuntimeError: cannot set daemon status of active thread
这里面展示的源码在上面blog👆中有说到,具体可参考
time.sleep
这里面还有一个有意思的函数time.sleep(),它的作用就是挂起,但是和sys.stdout.flush()结合起来就有一些其他神奇效果
sys.stdout.flush()
栗子来自blog------------------>sys.stdout.flush()的作用
import time
for i in range(5):
print(i)
time.sleep(1)
结果很简单
0
1
2
3
4
并且是一个一个打印出现来的
当代码改成
import time
for i in range(5):
print(i, end=' ')
time.sleep(1)
结果为
0 1 2 3 4
且是等待5s后一次性全部蹦出来的
给人的感觉像是print是以行(即/n)为结束,遇到行结束就打印出来,具体原因在上面👆blog中有说
将代码改为
import time
import sys
for i in range(5):
print(i, end=' ')
sys.stdout.flush()
time.sleep(1)
输出的最终结果与上面无异,但是效果和第一种很像,一个一个打印出来的
拓展:实现一个时钟,不停刷新,但是不换行
while True:
from time import strftime
from datetime import datetime
# print (strftime("%m/%d/%Y %H:%M:%S"), end="", flush=True)
now = datetime.now()
print ("%s/%s/%s %s:%s:%s" % (now.month,now.day,now.year,now.hour,now.minute,now.second),end="", flush=True)
print("\r", end="", flush=True)
time.sleep(1)
这里面的技巧主要在\r上,即光标在行首,参考-------------->python \r
将代码的print时间后面再加上一行
time.sleep即可看出端倪。
multiprocessing
由于这个包和threading很相似,所以放在一起
主要栗子来自multiprocessing python2.x
Process
和thread很像
pool
同时启动大量子进程的进程池
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print( 'Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print ('Parent process %s.' % os.getpid())
p = Pool()
for i in range(21):
p.apply_async(long_time_task, args=(i,))
print ('Waiting for all subprocesses done...')
p.close()
p.join()
print ('All subprocesses done.')
这里是21个进程同时启动,而默认为20,所以21号进程要等前面一个完成后才能执行,其中默认值就是CPU的核,具体解释在blog中
进程通信
也是使用blog中的例子
使用了multiprocessing中的queue
Bilibili lesson
进程线程
比喻
- 车间—进程
- 工人—线程
- 进程储存数据
- 线程一行一行执行
- python中,默认创建一个进程,进程又会创建一个线程,进程是资源分配单位而进程是真正工作单位
- 创建进程资源消耗少?----->对!
- 使用python threading创建线程以及start()之后,主线程会继续往下走不会等待
- window主进程是spawn,linux为fork,mac默认为spawn也可set为fork(如果是fork就不用写到main里面)
线程
GIL锁
全局解释器锁
- 把进程中的线程🔒住,一次只执行一次CPU
- 发挥多CPU核优势------>多进程
- 计算密集型->多进程
- IO密集型->多线程(例如网络交换是通过网卡直接发送,与CPU关系不大)
多线程开发
- 从在创建线程时,顺序为
- 创建程序主进程
- 主进程创建主线程执行code
- 执行到创建线程时,由主进程创建子进程
- 子进程创建子线程
- 主线程执行到code底端,等待所有子线程执行完毕
- end主线程进程
- 线程常用方法
- t.start()当前线程准备就绪,等待CPU调度,具体需要CPU来决定(与操作系统有关)
- t.join()等待线程的任务执行完毕后再向下继续执行
- Join()的案例,与CPU中断切换有关,可能计算没做完,直接切到其他进程中了,这样会导致数据混乱。例如多人抢票时票的数量
- serDaemon:主线程不等子线程 为Ture时
- 为线程命名,for debuging
- setName
- Threading.current_thread().getName()当前线程名
- 自定义线程类(写成threading.Thread的子类),直接将线程需要做的事情写到run方法中,通过self._args调用原线程的args的元组
线程安全
切换CPU时造成的数据安全,通过加锁实现 threading.RLock()
- 申请锁lock_object.acquire()
- 释放锁lock_object.release()
- 也可通过上下文管理即 with lock_object:
- python给有些数据类型自动上锁,这些数据类型称为
线程安全型
线程锁
lock和RLock
- lock()效率较高,不支持多次锁
- RLock()支持多次锁
- 多次锁的情况:协同开发时锁的嵌套
死锁
抢锁或者一个线程拿两把锁时会出现(可以通过Rlock()解决)
线程池
-
pool=ThreadPoolExecutor(pool_size)
-
Pool.submit(func,arg1,arg2…)
-
Pool.shutdown(True)类似于join()
-
future=pool.submit(…)
通过feature.result()拿到结果pool的结果
future=add_done_callback(another func)
任务分工
-
闭包
单例模式
- python在创建类对象的时候会先调用__new__方法来创建,然后再调用__init__方法来初始化(赋值)
- 在类中要写一个instance变量,然后在__new__根据instance进行创建
- __new__中变量铜鼓cls.xxx调用
- 每次实例化对象调用的地址都是一样的
进程
进程与进程之间相互隔离,不共享资源(不同APP之间资源不共享)
创建进程
- fork:拷贝所有资源
- 但是是拷贝在创建进程时刻的资源(等于两份),创建之后不会同步
- 支持文件对象/线程锁等传参
- spawn:只是创建一个python解释器,就是最简单创建一个进程实现一个函数功能
- 手动传递必备资源,传递资源通过拷贝生成
- 需要主进程中的数值时要通过args参数传递
- 不支持文件对象/线程锁传参
- forkserver:只传run()方法的必备资源,CPU执行进程时其实只是执行run方法
- 不支持文件对象/线程锁传参
进程模式
关于文件读写
- 文件读写是会先写到内存中,也即进程中,在进程结束后再刷到硬盘里(也即文件里)
- .flush()可以将内存中数据直接刷到硬盘中
- fork会将主进程中的内存数据(也即即将写到文件的数据)拷贝出来
关于锁
- 子进程中fork主进程中的锁,这把锁会被子进程的主线程申请走(如果在主线程中acquire了)
进程常见功能
-
start()
-
join()
-
setDeamon
-
进程名
- name
- Multiprocessing.current_process().name
- PID
- os.getpid()获取进程id
- os.getppid()获取父进程
- Threading.enumerate()
-
自定义进程类
- 需要自定义run函数,和线程类似
-
进程个数与CPU个数一致
- Multiprocessing.cpu_count()
进程间数据的共享
共享shared memory
- Value(c风code格)来实现数据共享,需要提前定义好变量类型
- Array,不仅需要提前定义类型还不能动数组大小
- manager,将字典dict()(key-value类型)和list()等python风格的数据类型集合加入
交换
-
通过队列queue,queue=multiprocessing.Queue
-
- put
- get
-
Pipe管道,parent_conn, child_conn=multiprocessing.Pipe
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6WJt6jSu-1641560154041)(/Users/xiaqiqi/Library/Application Support/typora-user-images/image-20220107155323952.png)]
-
其他方式
- 数据库
- 文件
- redis
进程锁
当多个程序公用资源时需要使用
lock=multiprocessing.Rlock() ###进程锁可以在进程间通过参数传递
使用spawn的进程并发
for i in range(num_process):
p=multiprocessing.Process(target=task,args=(lock,))
p.start()
process_list.append(p)
for proc in process_list:
proc.join()
进程池
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
pool = ProcessPoolExecutor(n_Process)
pool.shutdown(Ture)##使进程池与主进程进行同步
加入回调函数
fur = pool.submit(task, i)
fur.add_done_callback(done)#与线程最大的区别在于,回调的done函数会通过主进程来完成,可以通过multiprocessing.current_process().pid来验证
进程池与锁
不要用RLock要用Manager
协程
进程、线程在计算机中真实存在,而协程则是二次开发的,等同于微线程
实现代码切换
- 遇到IO等待的时候可以自动切换,解决IO下载等待问题