进程线程深入理解
发布时间:2022-09-21 14:57:38 所属栏目:Unix 来源:
导读: threading in os
在这里插入图片描述
所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚
在这里插入图片描述
所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚
|
threading in os 在这里插入图片描述 所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。 在这里插入图片描述 对比维度多进程多线程总结 数据共享、同步 数据共享复杂,同步简单 数据共享简单,同步复杂 各有优劣 内存、CPU 占用内存多,切换复杂,CPU利用率低 占用内存少,切换简单,CPU利用率高 线程占优 创建、销毁、切换 复杂,速度慢 简单,速度快 线程占优 编程、调试 编程简单,调试简单 编程复杂,调试复杂 进程占优 可靠性 进程间不会互相影响 一个线程挂掉将导致整个进程挂掉 进程占优 分布式 适用于多核、多机,扩展到多台机器简单 适合于多核 进程占优 GIL in python 在这里插入图片描述 在多线程环境中,Python 虚拟机按以下方式执行: 设置GIL 切换到一个线程去运行 运行直至指定数量的字节码指令,或者线程主动让出控制(可以调用sleep(0)) 把线程设置为睡眠状态 解锁GIL 再次重复以上所有步骤 在这里插入图片描述 multiprocessing Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(父进程)复制了一份(子进程),然后unix线程切换,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getpid()就可以拿到父进程的ID。 spawn和fork区别 fork:除了必要的启动资源外,其他变量,包,数据等都继承自父进程,并且是copy-on-write的,也就是共享了父进程的一些内存页,因此启动较快,但是由于大部分都用的父进程数据,所以是不安全的进程 spawn:从头构建一个子进程,父进程的数据等拷贝到子进程空间内,拥有自己的Python解释器,所以需要重新加载一遍父进程的包,因此启动较慢,由于数据都是自己的,安全性较高 方法名 spawn 父进程会启动一个全新的 python 解释器进程。 子进程将只继承那些运行进程对象的方法所必需的资源。 特别地,来自父进程的非必需文件描述符和句柄将不会被继承。 使用此方法启动进程相比使用fork或forkserver要慢上许多。 可在Unix和Windows上使用。 Windows上的默认设置。 fork 父进程使用来产生 Python 解释器分叉。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全分叉多线程进程是棘手的。 只存在于Unix。Unix中的默认值。 forkserver 程序启动并选择* forkserver * 启动方法时,将启动服务器进程。从那时起,每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。分叉服务器进程是单线程的,因此使用是安全的。没有不必要的资源被继承。 可在Unix平台上使用,支持通过Unix管道传递文件描述符。 注意事项 子进程不共享父进程的变量 在这里插入图片描述 from multiprocessing import Process import time // define global str_list str_list = ['ppp', 'yyy'] def add_str1(): """子进程1""" print('In process one: ', str_list) for x in 'thon': str_list.append(x * 3) time.sleep(1) print('In process one: ', str_list) def add_str2(): """子进程1""" print('In process two: ', str_list) for x in 'thon': str_list.append(x) time.sleep(1) print('In process two: ', str_list) if __name__ == '__main__': p1 = Process(target=add_str1) p1.start() p2 = Process(target=add_str2) p2.start() p1.join() p2.join() ---------------------------------------------------- In process one: ['ppp', 'yyy'] In process two: ['ppp', 'yyy'] In process one: ['ppp', 'yyy', 'ttt'] In process two: ['ppp', 'yyy', 't'] In process two: In process one: ['ppp', 'yyy', 'ttt', 'hhh']['ppp', 'yyy', 't', 'h'] In process one: In process two: ['ppp', 'yyy', 'ttt', 'hhh', 'ooo']['ppp', 'yyy', 't', 'h', 'o'] In process one: ['ppp', 'yyy', 'ttt', 'hhh', 'ooo', 'nnn'] In process two: ['ppp', 'yyy', 't', 'h', 'o', 'n'] 使用pool时multiprocessing.lock不能被序列化 在这里插入图片描述 pool方法使用了queue.Queue将task传递给工作进程,所以传递的数据会被序列化然后插入到队列中。而lock是一个对象,并不是str类型,对象无法插入到队列中,所以会报错。 import os import multiprocessing import time def write_file(lock): print(os.getpid(), os.getppid()) lock.acquire() with open('./t.log', 'a') as f: f.write("test") lock.release() if __name__ == '__main__': lock = multiprocessing.Lock() pool = multiprocessing.Pool(processes=5) for i in range(5): handler = pool.apply_async(write_file, (lock, )) # print(handler.get()) try: while True: time.sleep(3600) continue except KeyboardInterrupt: pool.close() pool.join() 平台差异: 对于linux和mac主进程执行的代码不会进程拷贝,但是对应windows系统来说,主进程执行的代码 也会进行拷贝,对于windows来说,创建子进程的代码如果进程拷贝执行相当于递归无限制进行创建子进程,会报错。 这也就是为什么:在windows中Process()必须放到if name == ‘main’:下 多线程 or 多进程写入同一文件并不会出现格式错乱 在实际使用过程中,不管是使用多线程还是多进程同时写入一个文件,都不会造成文件的格式错乱,似乎所有的写入操作都是原子操作在Linux下之所以多线程 or 多进程写入同一个文件没有出现异常是因为系统的一些机制 Appending to a File from Multiple Processes对于多线程 or 多进程同时写文件的操作,最好的方式还是加锁或者使用队列,在用户态对写入操作进行控制,这是万无一失的方式。 全局变量不可共享解决方案 想要进程间共享参数, 可以使用 from multiprocessing import Process,Manager,Manager支持的类型有:list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array 管理器是独立运行的子进程,其中存在真实的对象,并以服务器的形式运行,其他进程通过使用代理访问共享对象,这些代理作为客户端运行。Manager()是BaseManager的子类,返回一个启动的SyncManager()实例,可用于创建共享对象并返回访问这些共享对象的代理。 from multiprocessing import Process, Array import time # 创建共享内存,存入列表 shm = Array('i', [1,2,3,4,5]) # shm = Array('i', range(5)) # shm = Array('i', 5) # 表示开辟5个空间 def fun(shm): # shm 是可迭代对象 for i in shm: print(i) # 修改共享内存 print(list(shm)) shm[3] = 1000 if __name__ == '__main__': p = Process(target=fun, args=(shm,)) p.start() p.join() print("=================") for i in shm: print(i) 使用pool方法的initializer参数(pool不建议使用,方法有缺陷) import os import multiprocessing import time def write_file(): print(os.getpid(), os.getppid()) lock.acquire() with open('./t.log', 'a') as f: f.write("test") lock.release() def init_lock(l): global lock lock = l if __name__ == '__main__': l = multiprocessing.Lock() pool = multiprocessing.Pool(processes=5, initializer=init_lock, initargs=(l, )) for i in range(5): handler = pool.apply_async(write_file) print(handler.get()) try: while True: time.sleep(3600) continue except KeyboardInterrupt: pool.close() pool.join() uwsgi+django和锁 django原生是但进程多线程的web serveruwsgi会启动多个worker,这样一来django就变成了多进程多线程web server 这样该如何加锁呢? 需求&前情提要 linux和windows创建子进程的方式不同,充分证明windows干脆把文件都复制了一遍 from multiprocessing import Process import os def run_proc(name): print('Run child process %s (%s)...' % (name, os.getpid())) if __name__ == '__main__': print('Parent process %s.' % os.getpid()) p = Process(target=run_proc, args=('test',)) print('Child process will start.') p.start() p.join() print('Child process end.') print(1) ---------------------------------------------Linux Parent process 3268. Child process will start. Run child process test (3269)... Child process end. 1 ---------------------------------------------windows Parent process 10424. Child process will start. Child process end. 1 Run child process test (6540)... Child process end. 1 普通全局变量不共享 进程池中的进程并不是由当前同一个父进程创建的 用进程池模拟多个用户 实现: main.py # 程序入口 from g import mutex from multiprocessing import Process from time import sleep def user_operation(username): with mutex: print("%s 抢到了锁 %s,开始执行" % (username, hex(id(mutex)))) print("file upload starting") sleep(3) print("file upload ending") if __name__ == '__main__': users = [] for i in range(4): user = Process(target=user_operation, args=("user-%s" % i,)) user.start() users.append(user) for user in users: user.join() g.py # 全局变量 from multiprocessing import Manager,Lock mutex = Manager().Lock() 或者 mutex = Lock() 在这里插入图片描述 这段代码在windows环境会报错 An attempt has been made to start a new process before the current process has finished its bootstrapping phase. This probably means that you are not using fork to start your child processes and you have forgotten to use the proper idiom in the main module: if __name__ == '__main__': freeze_support() ... The "freeze_support()" line can be omitted if the program is not going to be frozen to produce an executable. 在这里插入图片描述 修改为: from multiprocessing import Process from time import sleep def user_operation(username, mu): with mu: print("%s 抢到了锁 %s,开始执行" % (username, hex(id(mu)))) print("file upload starting") sleep(3) print("file upload ending") if __name__ == '__main__': from g import mutex users = [] for i in range(4): user = Process(target=user_operation, args=("user-%s" % i, mutex)) user.start() users.append(user) for user in users: user.join() ---------------------------------------------------------- user-0 抢到了锁 0x156c7a12310,开始执行 file upload starting file upload ending user-1 抢到了锁 0x27b5fd72310,开始执行 file upload starting file upload ending user-2 抢到了锁 0x27ea9202310,开始执行 file upload starting file upload ending user-3 抢到了锁 0x204c3f91310,开始执行 file upload starting file upload ending 实现了相同的效果,但打印出来的锁的id每次都不同。 go 语言实现的效果与python在linux上的相同,是正确的。 package main import ( "fmt" "sync" "time" ) var a []string func init() { a = make([]string,0,5) } func userOperation(username string,wg *sync.WaitGroup,mutex *sync.Mutex){ defer wg.Done() mutex.Lock() fmt.Printf("user %s 抢到了锁\n", username) fmt.Println("file upload starting") fmt.Println(mutex) a = append(a,username) time.Sleep(time.Second*3) fmt.Println("file upload ending") mutex.Unlock() } func main() { mutex :=sync.Mutex{} wg:=sync.WaitGroup{} wg.Add(4) for i:=0;i<4;i++{ go userOperation(fmt.Sprintf("user-%d",i),&wg,&mutex) } wg.Wait() fmt.Println(a) } (编辑:百客网 - 百科网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
