疯狂python笔记--并发编程

单线程编程是一条顺序执行流,从上到下依次执行代码,如果遇到阻塞程序将会停滞在这里。

        当然,单线程十分有限,多线程相当于使用多个执行流且互相并不会发生干扰,多线程编程包含有创建、启动线程、控制线程三个方面。

线程与进程

        进程包含有三个特征:独立性(独立的实体,拥有自己独立的资源,并有私有地址空间,未经过允许其他进程无法直接访问地址空间)、动态性(是静态指令,通过加入时间概念,使得进程具有生命周期)、并发性(多个进程可以在单一处理器并发执行,互不干扰)。

线程:轻量级的进程,是进程的执行单元,独立、并发的执行流互相影响:一个线程可以创建和撤销另外一个线程。共享内存、文件句柄和其他进程应有的状态。

线程共享环境有:进程代码段、进程公有数据,方便实现通信

线程创建和启动

使用_thread和threading支持多线程。

#线程
import threading
def action(max):
    for i in range(max):
        print(threading.current_thread().getName() + " " + str(i) )
for i in range(100):
    print(threading.current_thread().getName() + " " + str(i))
    if i == 20:
        t1 = threading.Thread(target = action, args = (100,))
        t1.start()
        t2 = threading.Thread(target = action, args = (100,))
        t2.start()
print("main progess mission complete")

创建循环执行线程。多线程意义:如果不使用多线程,主程序直接调用两次action()函数,程序必须等第一次调用action()执行完成,才会执行第二次调用。

当然可以创建类继承线程thread

mport threading

class FkThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.i = 0
    def run(self):
        while self.i < 20:
            print(threading.current_thread().getName() + " " + str(i) )
            self.i += 1
for i in range(20):
    print(threading.current_thread().getName() + " " + str(i) )
    if i == 10:
        ft1 = FkThread()
        ft1.start()
        ft2 = FkThread()
        ft2.start()
print("main process mission complete")

线程生命周期

CPU需要在多个线程之间切换,所以线程也将多次在运行、就绪之间转换。

新建、就绪、运行、阻塞、死亡五个状态。

新建

        程序主要为主程序,线程进入就绪状态,等待执行。

import threading 
def action(max):
    for i in range(max):
        print(threading.current_thread().getName() + " " + str(i) )
for i in range(20):
    print(threading.current_thread().getName() + " " + str(i) )
    if i == 5:
        threading.Thread(target = action, args= (20, )).run()
        threading.Thread(target = action, args= (20, )).run()

运行和阻塞

        就绪状态线程得到CPU,执行run()方法的线程,线程处于运行状态。

  1.  调用sleep()方法主动放弃所占用处理器资源,线程调用一个阻塞式I/O方法,返回之前线程被阻塞。
  2.  试图获得一个锁对象,但是锁对象被其他线程持有。
  3.  等待某个通知。

线程死亡

        抛出错误或者正常结束,用is_alive()方法判断是否死亡。

控制线程

join线程

将一个大问题分解成小问题,在同一个组里面分配不同线程执行。

import threading
def action(max):
    for i in range(max):
        print(threading.current_thread().getName() + " " + str(i) )
#启动子线程
threading.Thread(target = action, args = (100,), name = "新线程").start()
for i in range(100):
    if i == 20:
        jt = threading.Thread(target = action, args = (100,), name = "被join的线程")
        jt.start()
        #线程等jt执行结束才向下执行
        jt.join()
    print(threading.current_thread().name + " " + str(i))

后台线程

当所有的前台线程死亡后,后台线程也会自动死亡。

import threading
def action(max):
    for i in range(max):
        print(threading.current_thread().name + " " + str(i))
t = threading.Thread(target = action, args = (100,), name= "后台线程")
#可以将线程设置为后台线程
t.daemon = True
t.start()
for i in range(10):
    print(threading.current_thread().name + " " + str(i))
    

线程同步

需要考虑线程安全问题,需要设置锁。

同步锁(Lock)

class X:
    def M():
        self.lock.acquire()
        try:
            pass
        finally:
            self.lock.release()

一个做一个简单的账户取钱程序。

import threading
import time

class Account:
    def __init__(self, account_no, balance):
        self.account_no = account_no
        self._balance = balance
        self.lock = threading.RLock()
    def getBalance(self):
        return self._balance
    def draw(self, draw_amount):
        self.lock.acquire()
        try:
            if self._balance >= draw_amount:
                print(threading.current_thread().name\
                     + "取钱成果,钞票:" + str(draw_amount))
                time.sleep(0.01)
                self._balance -= draw_amount
                print("\t 余额为:" + str(self._balance))
            else:
                print(threading.current_thread().name \
                     + "取钱失败,余额不足")
        finally:
            self.lock.release()
def draw(account, draw_amount):
    account.draw(draw_amount)
acct = Account("1234567", 100)
threading.Thread(name= "甲", target = draw , args =(acct, 800)).start()
threading.Thread(name= "乙", target = draw , args =(acct, 800)).start()

采用策略有:1.不要对线安全类所有方法都进行同步,只对那些改变竞争资源的方法同步。2.可变类有两种运行环境,单线程环境和多线程环境。

死锁

这个需要注意,时序和定时锁场景。

线程通信

使用队列控制线程通信

import threading
import time
import queue

def product(bq):
    str_tuple = ("python", "Kotlin", "Swift")
    for i in range(9999):
        print(threading.current_thread().name + "生产者准备生产")
        time.sleep(0.2)
        #将生产完产品放入队列,如果队列满了阻塞
        bq.put(str_tuple[i % 3])
        print(threading.current_thread().name + "生产者生产完成")
def consume(bq):
    while True:
        print(threading.current_thread().name + "消费者准备消费")
        time.sleep(0.2)
        #取出队列中的产品,如果队列为空,阻塞
        t = bq.get()
        print(threading.current_thread().name + "消费者消费[%s]完成" % t)
bq = queue.Queue(maxsize = 1)
threading.Thread(target = product, args = (bq,)).start()
threading.Thread(target = product, args = (bq,)).start()
threading.Thread(target = product, args = (bq,)).start()

threading.Thread(target = consume, args = (bq,)).start()

用Event可以实现事件以控制线程通信效果

import threading
import time
event = threading.Event()
def cal(name):
    print("%s 启动" % threading.currentThread().getName())
    print("%s 准备开始计算" % name)
    event.wait()
    print("%s 收到通知" % threading.currentThread().getName())
    print("%s 正式开始计算" % name)
threading.Thread(target = cal, args = ("甲",)).start()
threading.Thread(target = cal, args = ("乙",)).start()
time.sleep(2)
print("--------------------")
print("main thread event")
event.set()

线程池

线程池基类是concurrent.futures的Executor。使用线程池或者进程池并发编程,将相应的task函数提交给线程池或进程池就好。

程序将task函数提交到线程池,submit返回一个future对象,获取线程任务函数的返回值。

from concurrent.futures import ThreadPoolExecutor
import threading
import time

def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + " " + str(i))
        my_sum += i
    return my_sum
with ThreadPoolExecutor(max_workers = 2) as pool:
    future1 = pool.submit(action, 10)
    future2 = pool.submit(action, 20)
    def get_result(future):
        print(future.result())
    print(future1.done())
    time.sleep(3)
    print(future2.done())
    future1.add_done_callback(get_result)

用with上下文管理器进行分析,shoutdown节省关闭代码。

多进程

程序启动两个进程,一个是父进程,一个是子进程。

import multiprocessing
import os
def action(max):
    for i in range(max):
        print("(%s)子进程, 父进程(%s) %d" %(os.getpid(), os.getppid(),i))
if __name__ == "__main__":
    for i in range(100):
        print("(%s)子进程, %d" %(os.getpid(), i))
        if i == 20:
            mp1 = multiprocessing.Process(target = action, args = (100, ))
            mp1.start()
            mp2 = multiprocessing.Process(target = action, args = (100, ))
            mp2.start()
            mp2.join()
    print("main process mission complete")

当然,进程同样可以向线程一样进行继承。

import multiprocessing
import os

class MyProcess(multiprocessing.Process):
    def __init__(self, max):
        self.max = max
        super().__init__()
    def run(self):
        for i in range(self.max):
            print("(%s)子进程, 父进程(%s) %d" %(os.getpid(), os.getppid(),i))


if __name__ == "__main__":
    for i in range(100):
        print("(%s)子进程, %d" %(os.getpid(), i))
        if i == 20:
            mp1 = MyProcess(100)
            mp1.start()
            mp2 = MyProcess(100)
            mp2.start()
            mp2.run()
    print("main process mission complete")

Context和启动进程的方式

python支持三种方法启动进程,spawn:启动全新的python解释器。fork:子进程继承父进程所有资源,子进程基本等于父进程。forkserver:启动一个服务器进程。显示启动进程。

import multiprocessing
import os
def foo(q):
    print("被启动新进程(%s) %d" %(os.getpid(), i))
    q.put("python")
if __name__ == '__main__':
    ctx = multiprocessing.get_context('fork')
    q = ctx.Queue()
    mp = ctx.Process(target = foo, args = (q, ))
    mp.start()
    print(q.get())
    mp.join()

windows使用

multiprocessing.get_context('fork')

函数获取context进行内容设置。

你可能感兴趣的:(笔记)