进程和线程
一.多进程
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__'中写相关业务逻辑
浙公网安备 33010602011771号