13 线程和进程初探


进程和线程初探

概念

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

    一个进程还可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核CPU系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。使用多线程实现并发编程为程序带来的好处是不言而喻的,最主要的体现在提升程序的性能和改善用户体验,今天我们使用的软件几乎都用到了多线程技术,这一点可以利用系统自带的进程监控工具(如macOS中的“活动监视器”、Windows中的“任务管理器”)来证实。

  • python中有GIL锁,每次cpu只能执行一个线程,多线程的应用场景是不需要cpu算力的场景,多进程则是大量运算的场景。

  • 可以分为:多进程、多线程、多进程多线程

多进程

  • 单进程和多进程的区别

    #单进程和多进程的差别
    
    #单进程运行
    from random import randint
    from time import time,sleep
    
    def download_task(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()
        download_task('Python从入门到住院.pdf')
        download_task('Peking Hot.avi')
        end=time()
        print('总共耗费了%.2f秒.' % (end - start))
    
    if __name__ == '__main__':
        main()
    
    '''
    开始下载Python从入门到住院.pdf
    Python从入门到住院.pdf下载完成! 耗费了5秒
    开始下载Peking Hot.avi
    Peking Hot.avi下载完成! 耗费了5秒
    总共耗费了10.01秒.
    '''
    
    #多进程运行
    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=('文件1',))
        p1.start()
        p2=Process(target=download_task,args=('文件2',))
        p2.start()
        p1.join()
        p2.join()
        end=time()
    
    if __name__ == '__main__':
        main()
    '''
    启动进程,进程号为83376
    开始下载文件1...
    启动进程,进程号为83377
    开始下载文件2...
    文件2下载完成! 耗费了7秒
    文件1下载完成! 耗费了7秒
    总共耗费了7.02秒.
    '''
    

多线程

#	和多进程中一样,此时使用的模块有变化
	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()
    t2.join()
    end = time()
    print('总共耗费了%.3f秒' % (end - start))


if __name__ == '__main__':
    main()
'''
开始下载Python从入门到住院.pdf...
开始下载Peking Hot.avi...
Python从入门到住院.pdf下载完成! 耗费了9秒
Peking Hot.avi下载完成! 耗费了9秒
总共耗费了9.004秒
'''
  • 因为多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面的例子演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果,因此需要加锁。

    from time import sleep
    from threading import Thread,Lock
    
    class Account():
    
        def __init__(self):
            #这是一个余额是0的账户
            self._balance=0
            self._lock=Lock()
    
        @property
        def balance(self):
            return self._balance
    
        #存款行为
        def deposit(self,money):
            self._lock.acquire()
            try:
                new_balance=self._balance+money
                sleep(0.01)
                self._balance=new_balance
            finally:
                self._lock.release()
    
    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=[]
        #创建100个线程同时往账户里存钱
        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()