• 进程和线程


    一.多进程

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

  • 相关阅读:
    sprintf函数%u输入long long int型数值异常
    关于smarty模板display函数的$compile_id 参数的意义
    打log的时候如果少写一个%d,cgi会core掉
    c++标准库的源码和SIG实现是什么关系
    2010
    jquery 中jsonp原理最简说明
    1月17日stl string阅读笔记
    Moss母版页制作详解(一)
    Moss中的权限操作
    动态添加和删除表格行
  • 原文地址:https://www.cnblogs.com/itfenqing/p/10262103.html
Copyright © 2020-2023  润新知