进程和线程

一.多进程

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__'中写相关业务逻辑

 

posted @ 2019-01-13 11:03  rorshach  阅读(199)  评论(0编辑  收藏  举报