进程和线程
一.多进程
1.linux/unix提供了一个fork函数来创建进程.fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
在父进程中,fork返回新创建子进程的进程ID;
在子进程中,fork返回0;
如果出现错误,fork返回一个负值
2.python的os模块提供了fork函数(win下不可用)
>>> os.fork()
2160 #父进程id
>>> 0 #子进程
3.os进程常用方法:
os.fork() #创建进程
os.getpid() #获取进程id
os.getppid() #获取父进程id
二.multiprocessing
1.multiprocessing为多进程跨平台提供了支持,Process类代表一个进程对象.
示例代码:
import os from multiprocessing import Process def run_process(name): print('child %s run: pid is %s' % (name, os.getpid())) if __name__ == '__main__': print('parent process is: %s' % os.getpid()) p = Process(target=run_process, args=('child-test',)) print('child process start') p.start() p.join() print('child process end')
输出:
parent process is: 2335
child process start
child child-test run: pid is 2336
child process end
2.start: 启动进程
3.join:方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步
三.pool(进程池)
示例代码:
import os import time from multiprocessing import Pool def task(name): print('child %s: pid %s' % (name, os.getpid())) time.sleep(2) print(time.time()) if __name__ == '__main__': print('parent process: %s' % os.getpid()) p = Pool(4) for i in range(5): p.apply_async(task, args=(i,)) print('wait subprocess done') p.close() p.join() print('end')
备注:
进程池默认数量为创建cpu核心数的进程,只有数量大于或等于cpu核心数才能看到效果
四.子进程
示例代码:
import subprocess ret = subprocess.call(['ping', 'www.python.com']) print(ret)
子进程输入参数:
import subprocess process = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err = process.communicate(b'python.com\nexit') print(output) print(output.decode('utf-8', errors="ignore")) print('return value: %s' % process.returncode)
上面代码相当于在程序运行时输入
python.com
exit
五.进程间通信
利用queue,还可以使用pipes来做进程间通信
from multiprocessing import Process, Queue import os, time def write(q): print('write pid: %s' % os.getpid()) q.put('hello world') time.sleep(2) def read(q): print('read pid: %s' % os.getpid()) value = q.get(True) time.sleep(2) print(value) if __name__ == '__main__': q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) pw.start() pr.start() pw.join()
六.多线程
1._thread和threading
python标准库提供了_thread和threading两个模块,前者比较低级,后者比较高级,一般用threading模块
每个进程创建后会默认创建一个线程,这个线程就是主线程;threading.current_thread().name可以获得线程名称,主线程是MainThread
2.start启动线程
3.示例代码
import time, threading def run(p): time.sleep(1) print(p) tasks = [] for i in range(10): # target指定线程要执行的代码,args指定该代码的参数 t = threading.Thread(target=run, args=(i,)) tasks.append(t) for task in tasks: task.start() #启动线程 task.join() #确保子线程结束后,才结束主线程,线程按顺序执行 print(threading.current_thread().name)
七.Lock(线程锁):
1.问题来源
多进程中,同一个变量,各自有一份副本存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,导致数据不同步
2.示例代码:
import time, threading # 假定这是你的银行存款: balance = 0 def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n def run_thread(n): for i in range(100000): change_it(n) t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)
多运行几次,你会发现输出不是0
3.线程锁:
我们再关键操作上加锁,来保证线程对数据的更改是同步的.
import threading lock = threading.Lock() # 假定这是你的银行存款: balance = 0 def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n def run_thread(n): # 同一时刻只有一个线程可以锁定,更改数据 lock.acquire() try: for i in range(100000): change_it(n) finally: # 释放锁 lock.release() t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)
八.GIL锁
GIL锁:Global Interpreter Lock
任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。这导致python线程没法使用多核.
九.ThreadLocal
线程本地变量(其实是一个特殊的全局变量)
import threading # 类 class Dog: def __init__(self, name): self.__name = name @property def name(self): return self.__name # 创建线程本地变量 local_dog = threading.local() def process_dog(): dog = local_dog.dog print(dog.name) # 线程 def thread_task(name): local_dog.dog = Dog(name) process_dog() t1 = threading.Thread(target=thread_task, args=('dalang',), name='thread-a') t2 = threading.Thread(target=thread_task, args=('xiaolang',), name='thread-b') t1.start() t2.start() t1.join() t2.join()
输出:
dalang
xiaolang
解决的问题:
(1).解决了参数在一个线程中各个函数之间互相传递的问题
(2).ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰
十.分布式进程
1.master:
import random, queue from multiprocessing.managers import BaseManager # 任务队列 task_queues = queue.Queue() # 处理结果队列 result_queues = queue.Queue() # 从BaseManager继承的QueueManager: class QueueManager(BaseManager): pass def return_task_queue(): global task_queues return task_queues def return_result_queue(): global result_queues return result_queues if __name__ == '__main__': # 注册任务队列(windows下pickle模块不能序列化lambda函数,所以需要定义函数return_task_queue) QueueManager.register('get_task_queue', callable=return_task_queue) # 注册结果队列(windows下pickle模块不能序列化lambda函数,所以需要定义函数return_result_queue) QueueManager.register('get_result_queue', callable=return_result_queue) # 绑定端口 manager = QueueManager(address=('192.168.2.10', 5000), authkey=b'password') # 启动队列 manager.start() # 获得通过网络访问的Queue对象: task = manager.get_task_queue() result = manager.get_result_queue() # 推入任务 for i in range(10): n = random.randint(0, 10000) print('Put task %d...' % n) task.put(n) # 从result队列读取结果: print('Try get results...') for i in range(10): r = result.get(timeout=600) print('Result: %s' % r) # 关闭: manager.shutdown() print('master exit.')
2.work:
import time, sys, queue import multiprocessing from multiprocessing.managers import BaseManager # 创建类似的QueueManager: class QueueManager(BaseManager): pass # 由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字: QueueManager.register('get_task_queue') QueueManager.register('get_result_queue') # 连接到服务器,也就是运行task_master.py的机器: server_addr = '192.168.2.10' print('Connect to server %s...' % server_addr) # 端口和验证码注意保持与task_master.py设置的完全一致: m = QueueManager(address=(server_addr, 5000), authkey=b'password') # 从网络连接: m.connect() # 获取Queue的对象: task = m.get_task_queue() result = m.get_result_queue() # 从task队列取任务,并把结果写入result队列: for i in range(10): try: n = task.get(timeout=1) print('run task %d * %d...' % (n, n)) r = '%d * %d = %d' % (n, n, n*n) time.sleep(1) result.put(r) except Queue.Empty: print('task queue is empty.') # 处理结束: print('worker exit.')
注意:
master/work,所在服务器最好版本一致
在window下,__name__ == '__main__'中写相关业务逻辑