• python 多进程和多线程


    进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。进程可以通过fork或spawn的方式来创建新的进程来执行其他的任务,不过新的进程也有自己独立的内存空间,因此必须通过进程间通信机制(IPC,Inter-Process Communication)来实现数据共享,具体的方式包括管道、信号、套接字、共享内存区等。

    一个进程可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。

    由于线程在同一个进程下,它们可共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核CPU系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。

    Python中的多进程

    Unix和Linux操作系统上提供了fork()系统调用来创建进程,调用fork()函数的是父进程,创建出的是子进程,子进程是父进程的一个拷贝,但是子进程拥有自己的PID。fork()函数非常特殊它会返回两次,父进程中可以通过fork()函数的返回值得到子进程的PID,而子进程中的返回值永远都是0。

    Python的os模块提供fork()函数。由于Windows系统没有fork()调用,因此要实现跨平台的多进程编程,可以使用multiprocessing模块的Process类来创建子进程,而且该模块还提供了更高级的封装,例如批量启动进程的进程池(Pool)、用于进程间通信的队列(Queue)和管道(Pipe等。

    from multiprocessing import Process
    from os import getpid
    from random import randint
    from time import time, sleep
    
    def download_task(filename):
        print('启动下载进程,进程号[%d].' % getpid())
        print('开始下载%s...' % filename)
        time_to_download = randint(5, 10)
        sleep(time_to_download)
        print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
    
    def main():
        start = time()
        p1 = Process(target=download_task, args=('Python从入门到住院.pdf', ))
        p1.start()
        p2 = Process(target=download_task, args=('Peking Hot.avi', ))
        p2.start()
        p1.join()      # 等待进程执行结束
        p2.join()
        end = time()
        print('总共耗费了%.2f秒.' % (end - start))
    
    if __name__ == '__main__':
        main()

    # 用subprocess模块中的类和函数来创建和启动子进程,然后通过管道来和子进程通信

    全局变量counter不起作用!!--> 原因:进程各自持有一份数据,默认无法共享数据 -->  用multiprocessing模块中的Queue类,它是可以被多个进程共享的队列,底层是通过管道和信号量(semaphore)机制来实现的

    Python中的多线程

    Python解释器通过GIL(全局解释器锁)来防止多个线程同时执行本地字节码,这个锁对于CPython(Python解释器的官方实现)是必须的,因为CPython的内存管理并不是线程安全的。因为GIL的存在,Python的多线程并不能利用CPU的多核特性。

    from random import randint
    from threading import Thread
    from time import time, sleep
    
    def download(filename):
        print('开始下载%s...' % filename)
        time_to_download = randint(5, 10)
        sleep(time_to_download)
        print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
    
    def main():
        start = time()
        t1 = Thread(target=download, args=('Python从入门到住院.pdf',))
        t1.start()
        t2 = Thread(target=download, args=('Peking Hot.avi',))
        t2.start()
        t1.join()   # 逐个执行每个线程,执行完毕后main()继续往下执行.
        t2.join()
        end = time()
        print('总共耗费了%.3f秒' % (end - start))
    
    if __name__ == '__main__':
        main()

    继承Thread类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。

    from random import randint
    from threading import Thread
    from time import time, sleep
    
    class DownloadTask(Thread):
        def __init__(self, filename):
            super().__init__()
            self._filename = filename
      # 线程被cpu调度后自动执行线程对象的run方法
    def run(self): print('开始下载%s...' % self._filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download)) def main(): start = time() t1 = DownloadTask('Python从入门到住院.pdf') t1.start() t2 = DownloadTask('Peking Hot.avi') t2.start() t1.join() t2.join() end = time() print('总共耗费了%.2f秒.' % (end - start)) if __name__ == '__main__': main()
    • setName / getName / start()
    • join()   逐个执行每个线程,执行完毕后主线程继续往下执行.

    • setDaemon()   设置为后台线程或前台线程(默认)                   
      • 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;
      • 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止;

    多个线程共享进程的全局变量, 启用锁机制:

    from time import sleep
    from threading import Thread, Lock
    
    class Account(object):
        def __init__(self):
            self._balance = 0
            self._lock = Lock()
    
        def deposit(self, money):
            # 先获取锁才能执行后续的代码
            self._lock.acquire()
            try:
                new_balance = self._balance + money
                sleep(0.01)
                self._balance = new_balance
            finally:
                # 在finally中执行释放锁的操作保证正常异常锁都能释放
                self._lock.release()
    
        @property
        def balance(self):
            return self._balance
    
    
    class AddMoneyThread(Thread):
    
        def __init__(self, account, money):
            super().__init__()
            self._account = account
            self._money = money
    
        def run(self):
            self._account.deposit(self._money)
    
    
    def main():
        account = Account()
        threads = []
        for _ in range(100):
            t = AddMoneyThread(account, 1)
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
        print('账户余额为: ¥%d元' % account.balance)
    
    
    if __name__ == '__main__':
        main()

    Python在threading模块中定义了几种线程锁类,分别是: https://www.liujiangblog.com/course/python/79

    Lock 互斥锁 lk = threading.Lock()
    acquire() / release()
    RLock 可重入锁   对象内部维护着一个Lock和一个counter对象
    Semaphore 信号

    se= threading.BoundedSemaphore(5)

    允许一定数量的线程同时更改数据   acquire() / release()
    Event 事件  

    event = threading.Event()

     全局定义一个Flag,如果Flag=False,当程序执行wait()方法时就会阻塞,如果Flag=True,线程不再阻塞。

    clear()方法会将事件的Flag设置为False。  set()方法会将Flag设置为True。

    is_set()判断当前是否"绿灯放行"状态。    wait()方法将等待“红绿灯”信号。

    Condition 条件

     

    con = threading.Condition()

      acquire() / release()

    notify()   从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁(进入锁定池),其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常
    notifyAll() 将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。
    con.wait() 使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁,否则将抛出异常。

    Barrier “阻碍”

       acquire() / release()

    单线程+异步I/O

    异步编程是通过调度程序从任务队列中挑选任务,调度程序以交叉形式执行这些任务,由于执行时间和顺序的不确定,因此需要通过钩子函数(回调函数)或Future对象来获取任务执行的结果。Python 3通过asyncio模块以及awaitasync关键字提供了对异步I/O的支持。

    利用操作系统提供的异步I/O支持,就可用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。

    Nginx就是支持异步I/O的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式,这也是当下实现多任务编程的一种趋势。

    在Python语言中,单线程+异步I/O的编程模型称为协程,可基于事件驱动编写高效的多任务程序。

    1. 极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。
    2. 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好,所以执行效率比多线程高很多。

    如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

  • 相关阅读:
    SQL SERVER常用取重复记录的SQL语句
    订单号生成
    SQL Server 2005中返回修改后的数据
    一条sql语句,要修改一个字段的俩个值,比如把字段sex中的男改为女,女改为男
    C#输入法全半角转换
    dataGridView1 筛选
    C#(WIN FORM)两个窗体间LISTVIEW值的修改
    ms sql server 2005数据库日志文件过大,需要清除或者清空
    SQL 判断表是否存在
    C#将数据写入记事本并且从记事本中读出
  • 原文地址:https://www.cnblogs.com/bsszds930/p/12909252.html
Copyright © 2020-2023  润新知