BSSZDS930

博客园 首页 新随笔 联系 订阅 管理

进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。进程可以通过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的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

posted on 2020-05-18 11:18  BSSZDS930  阅读(140)  评论(0编辑  收藏  举报