day20-併發程式設計(下)

死不悔改奇男子發表於2024-04-23

1. 多程序開發

程序是計算機中資源分配的最小單元;一個程序中可以有多個執行緒,同一個程序中的執行緒共享資源;

程序與程序之間則是相互隔離。

Python中透過多程序可以利用CPU的多核優勢,計算密集型操作適用於多程序。

1.1 程序介紹

import multiprocessing

def task():
	pass

if __name__ == '__main__':
    p1 = multiprocessing.Process(target=task)
    p1.start()
import multiprocessing

def task(arg):
	pass

def run():
    p = multiprocessing.Process(target=task, args=('xxx',))
    p.start()

if __name__ == '__main__':
    run()

關於在Python中基於multiprocessiong模組操作的程序:

Depending on the platform, multiprocessing supports three ways to start a process. These start methods are

  • fork,【“複製”幾乎所有資源】【支援檔案物件/執行緒鎖等傳參】【unix】【任意位置開始】【快】

    The parent process uses os.fork() to fork the Python interpreter. The child process, when it begins, is effectively identical to the parent process. All resources of the parent are inherited by the child process. Note that safely forking a multithreaded process is problematic.Available on Unix only. The default on Unix.

  • spawn,【run引數傳必備資源】【不支援檔案物件/執行緒鎖等傳參】【unix、win】【main程式碼塊開始】【慢】

    The parent process starts a fresh python interpreter process. The child process will only inherit those resources necessary to run the process object’s run() method. In particular, unnecessary file descriptors and handles from the parent process will not be inherited. Starting a process using this method is rather slow compared to using fork or forkserver.Available on Unix and Windows. The default on Windows and macOS.

  • forkserver,【run引數傳必備資源】【不支援檔案物件/執行緒鎖等傳參】【部分unix】【main程式碼塊開始】

    When the program starts and selects the forkserver start method, a server process is started. From then on, whenever a new process is needed, the parent process connects to the server and requests that it fork a new process. The fork server process is single threaded so it is safe for it to use os.fork(). No unnecessary resources are inherited.Available on Unix platforms which support passing file descriptors over Unix pipes.

import multiprocessing
multiprocessing.set_start_method("spawn")

Changed in version 3.8: On macOS, the spawn start method is now the default. The fork start method should be considered unsafe as it can lead to crashes of the subprocess. See bpo-33725.

Changed in version 3.4: spawn added on all unix platforms, and forkserver added for some unix platforms. Child processes no longer inherit all of the parents inheritable handles on Windows.

On Unix using the spawn or forkserver start methods will also start a resource tracker process which tracks the unlinked named system resources (such as named semaphores or SharedMemory objects) created by processes of the program. When all processes have exited the resource tracker unlinks any remaining tracked object. Usually there should be none, but if a process was killed by a signal there may be some “leaked” resources. (Neither leaked semaphores nor shared memory segments will be automatically unlinked until the next reboot. This is problematic for both objects because the system allows only a limited number of named semaphores, and shared memory segments occupy some space in the main memory.)

官方文件:https://docs.python.org/3/library/multiprocessing.html

  • 示例1

    import multiprocessing
    import time
    
    """
    def task():
        print(name)
        name.append(123)
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
        name = []
    
        p1 = multiprocessing.Process(target=task)
        p1.start()
    
        time.sleep(2)
        print(name)  # []
    """
    """
    def task():
        print(name) # [123]
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
        name = []
        name.append(123)
    
        p1 = multiprocessing.Process(target=task)
        p1.start()
    """
    
    """
    def task():
        print(name)  # []
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
        name = []
        
        p1 = multiprocessing.Process(target=task)
        p1.start()
    
        name.append(123)
        
    """
    
    
  • 示例2

    import multiprocessing
    
    
    def task():
        print(name)
        print(file_object)
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("fork")  # fork、spawn、forkserver
        
        name = []
        file_object = open('x1.txt', mode='a+', encoding='utf-8')
    
        p1 = multiprocessing.Process(target=task)
        p1.start()
    
    

案例:

import multiprocessing


def task():
    print(name)
    file_object.write("alex\n")
    file_object.flush()


if __name__ == '__main__':
    multiprocessing.set_start_method("fork")
    
    name = []
    file_object = open('x1.txt', mode='a+', encoding='utf-8')
    file_object.write("武沛齊\n")

    p1 = multiprocessing.Process(target=task)
    p1.start()
	
    >>> 武沛齊
    >>> alex
    >>> 武沛齊
import multiprocessing


def task():
    print(name)
    file_object.write("alex\n")
    file_object.flush()


if __name__ == '__main__':
    multiprocessing.set_start_method("fork")
    
    name = []
    file_object = open('x1.txt', mode='a+', encoding='utf-8')
    file_object.write("武沛齊\n")
    file_object.flush()

    p1 = multiprocessing.Process(target=task)
    p1.start()

    >>> 武沛齊
    >>> alex
import multiprocessing
import threading
import time

def func():
    print("來了")
    with lock:
        print(666)
        time.sleep(1)

def task():
    # 複製的鎖也是被申請走的狀態
    # 被誰申請走了? 被子程序中的主執行緒申請走了
    for i in range(10):
        t = threading.Thread(target=func)
        t.start()
    time.sleep(2)
    lock.release()


if __name__ == '__main__':
    multiprocessing.set_start_method("fork")
    name = []
    lock = threading.RLock()
    lock.acquire()
    # print(lock)
    # lock.acquire() # 申請鎖
    # print(lock)
    # lock.release()
    # print(lock)
    # lock.acquire()  # 申請鎖
    # print(lock)

    p1 = multiprocessing.Process(target=task)
    p1.start()

1.2 常見功能

程序的常見方法:

  • p.start(),當前程序準備就緒,等待被CPU排程(工作單元其實是程序中的執行緒)。

  • p.join(),等待當前程序的任務執行完畢後再向下繼續執行。

    import time
    from multiprocessing import Process
    
    
    def task(arg):
        time.sleep(2)
        print("執行中...")
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("spawn")
        p = Process(target=task, args=('xxx',))
        p.start()
        p.join()
    
        print("繼續執行...")
    
  • p.daemon = 布林值,守護程序(必須放在start之前)

    • p.daemon =True,設定為守護程序,主程序執行完畢後,子程序也自動關閉。
    • p.daemon =False,設定為非守護程序,主程序等待子程序,子程序執行完畢後,主程序才結束。
    import time
    from multiprocessing import Process
    
    
    def task(arg):
        time.sleep(2)
        print("執行中...")
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("spawn")
        p = Process(target=task, args=('xxx',))
        p.daemon = True
        p.start()
    
        print("繼續執行...")
    
    
  • 程序的名稱的設定和獲取

    import os
    import time
    import threading
    import multiprocessing
    
    
    def func():
        time.sleep(3)
    
    
    def task(arg):
        for i in range(10):
            t = threading.Thread(target=func)
            t.start()
        print(os.getpid(), os.getppid())
        print("執行緒個數", len(threading.enumerate()))
        time.sleep(2)
        print("當前程序的名稱:", multiprocessing.current_process().name)
    
    
    if __name__ == '__main__':
        print(os.getpid())
        multiprocessing.set_start_method("spawn")
        p = multiprocessing.Process(target=task, args=('xxx',))
        p.name = "哈哈哈哈"
        p.start()
    
        print("繼續執行...")
    
    
  • 自定義程序類,直接將執行緒需要做的事寫到run方法中。

    import multiprocessing
    
    
    class MyProcess(multiprocessing.Process):
        def run(self):
            print('執行此程序', self._args)
    
    
    if __name__ == '__main__':
        multiprocessing.set_start_method("spawn")
        p = MyProcess(args=('xxx',))
        p.start()
        print("繼續執行...")
    
    
  • CPU個數,程式一般建立多少個程序?(利用CPU多核優勢)。

    import multiprocessing
    multiprocessing.cpu_count()
    
    import multiprocessing
    
    if __name__ == '__main__':
        count = multiprocessing.cpu_count()
        for i in range(count - 1):
            p = multiprocessing.Process(target=xxxx)
            p.start()
    

2.程序間資料的共享

程序是資源分配的最小單元,每個程序中都維護自己獨立的資料,不共享。

import multiprocessing


def task(data):
    data.append(666)


if __name__ == '__main__':
    data_list = []
    p = multiprocessing.Process(target=task, args=(data_list,))
    p.start()
    p.join()

    print("主程序:", data_list) # []

如果想要讓他們之間進行共享,則可以藉助一些特殊的東西來實現。

2.1 共享

Shared memory

Data can be stored in a shared memory map using Value or Array. For example, the following code

    'c': ctypes.c_char,  'u': ctypes.c_wchar,
    'b': ctypes.c_byte,  'B': ctypes.c_ubyte,
    'h': ctypes.c_short, 'H': ctypes.c_ushort,
    'i': ctypes.c_int,   'I': ctypes.c_uint,  (其u表示無符號)
    'l': ctypes.c_long,  'L': ctypes.c_ulong,
    'f': ctypes.c_float, 'd': ctypes.c_double
from multiprocessing import Process, Value, Array


def func(n, m1, m2):
    n.value = 888
    m1.value = 'a'.encode('utf-8')
    m2.value = "武"


if __name__ == '__main__':
    num = Value('i', 666)
    v1 = Value('c')
    v2 = Value('u')

    p = Process(target=func, args=(num, v1, v2))
    p.start()
    p.join()

    print(num.value)  # 888
    print(v1.value)  # a
    print(v2.value)  # 武
from multiprocessing import Process, Value, Array


def f(data_array):
    data_array[0] = 666


if __name__ == '__main__':
    arr = Array('i', [11, 22, 33, 44]) # 陣列:元素型別必須是int; 只能是這麼幾個資料。

    p = Process(target=f, args=(arr,))
    p.start()
    p.join()

    print(arr[:])	# [666, 22, 33, 44]

Server process

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

from multiprocessing import Process, Manager

def f(d, l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None
    l.append(666)

if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list()

        p = Process(target=f, args=(d, l))
        p.start()
        p.join()

        print(d)	# {1: '1', '2': 2, 0.25: None}
        print(l)	# [666]

2.2 交換

multiprocessing supports two types of communication channel between processes

Queues

image

The Queue class is a near clone of queue.Queue. For example

import multiprocessing


def task(q):
    for i in range(10):
        q.put(i)


if __name__ == '__main__':
    queue = multiprocessing.Queue()
    
    p = multiprocessing.Process(target=task, args=(queue,))
    p.start()
    p.join()

    print("主程序")
    print(queue.get())
    print(queue.get())
    print(queue.get())
    print(queue.get())
    print(queue.get())

Pipes

image

The Pipe() function returns a pair of connection objects connected by a pipe which by default is duplex (two-way). For example:

import time
import multiprocessing


def task(conn):
    time.sleep(1)
    conn.send([111, 22, 33, 44])
    data = conn.recv() # 阻塞
    print("子程序接收:", data)
    time.sleep(2)


if __name__ == '__main__':
    parent_conn, child_conn = multiprocessing.Pipe()

    p = multiprocessing.Process(target=task, args=(child_conn,))
    p.start()

    info = parent_conn.recv() # 阻塞
    print("主程序接收:", info)
    parent_conn.send(666)

上述都是Python內部提供的程序之間資料共享和交換的機制,作為了解即可,在專案開發中很少使用,後期專案中一般會藉助第三方的來做資源的共享,例如:MySQL、redis等。

image

3. 程序鎖

如果多個程序搶佔式去做某些操作時候,為了防止操作出問題,可以透過程序鎖來避免。

import time
from multiprocessing import Process, Value, Array


def func(n, ):
    n.value = n.value + 1


if __name__ == '__main__':
    num = Value('i', 0)

    for i in range(20):
        p = Process(target=func, args=(num,))
        p.start()

    time.sleep(3)
    print(num.value)	# 答案不一定是20
import time
from multiprocessing import Process, Manager


def f(d, ):
    d[1] += 1


if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        d[1] = 0
        for i in range(20):
            p = Process(target=f, args=(d,))
            p.start()
        time.sleep(3)
        print(d)	# 答案不一定是20
import time
import multiprocessing


def task():
    # 假設檔案中儲存的內容就是一個值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())

    print("排隊搶票了")
    time.sleep(1)

    current_num -= 1
    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))


if __name__ == '__main__':
    for i in range(20):
        p = multiprocessing.Process(target=task)
        p.start()

很顯然,多程序在操作時就會出問題,此時就需要鎖來介入:

import time
import multiprocessing


def task(lock):
    print("開始")
    lock.acquire()
    # 假設檔案中儲存的內容就是一個值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())

    print("排隊搶票了")
    time.sleep(0.5)
    current_num -= 1

    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
    lock.release()


if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    
    lock = multiprocessing.RLock() # 程序鎖
    
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(lock,))
        p.start()

    # spawn模式,需要特殊處理。
    time.sleep(7)


import time
import multiprocessing
import os


def task(lock):
    print("開始")
    lock.acquire()
    # 假設檔案中儲存的內容就是一個值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())

    print(os.getpid(), "排隊搶票了")
    time.sleep(0.5)
    current_num -= 1

    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
    lock.release()


if __name__ == '__main__':
    multiprocessing.set_start_method("spawn")
    lock = multiprocessing.RLock()

    process_list = []
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(lock,))
        p.start()
        process_list.append(p)

    # spawn模式,需要特殊處理。
    for item in process_list:
        item.join()
import time
import multiprocessing

def task(lock):
    print("開始")
    lock.acquire()
    # 假設檔案中儲存的內容就是一個值:10
    with open('f1.txt', mode='r', encoding='utf-8') as f:
        current_num = int(f.read())

    print("排隊搶票了")
    time.sleep(1)
    current_num -= 1

    with open('f1.txt', mode='w', encoding='utf-8') as f:
        f.write(str(current_num))
    lock.release()


if __name__ == '__main__':
    multiprocessing.set_start_method('fork')
    lock = multiprocessing.RLock()
    for i in range(10):
        p = multiprocessing.Process(target=task, args=(lock,))
        p.start()

4. 程序池

import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


def task(num):
    print("執行", num)
    time.sleep(2)


if __name__ == '__main__':
    # 修改模式
    pool = ProcessPoolExecutor(4)
    for i in range(10):
        pool.submit(task, i)
	print(1)
	print(2)

import time
from concurrent.futures import ProcessPoolExecutor


def task(num):
    print("執行", num)
    time.sleep(2)


if __name__ == '__main__':

    pool = ProcessPoolExecutor(4)
    for i in range(10):
        pool.submit(task, i)
	# 等待程序池中的任務都執行完畢後,再繼續往後執行。
    pool.shutdown(True)
    print(1)

import time
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

def task(num):
    print("執行", num)
    time.sleep(2)
    return num


def done(res):
    print(multiprocessing.current_process())
    time.sleep(1)
    print(res.result())
    time.sleep(1)


if __name__ == '__main__':

    pool = ProcessPoolExecutor(4)
    for i in range(50):
        fur = pool.submit(task, i)
        fur.add_done_callback(done) # done的呼叫由主程序處理(與執行緒池不同)
        
    print(multiprocessing.current_process())
    pool.shutdown(True)

注意:如果在程序池中要使用程序鎖,則需要基於Manager中的Lock和RLock來實現。

import time
import multiprocessing
from concurrent.futures.process import ProcessPoolExecutor


def task(lock):
    print("開始")
    # lock.acquire()
    # lock.relase()
    with lock:
        # 假設檔案中儲存的內容就是一個值:10
        with open('f1.txt', mode='r', encoding='utf-8') as f:
            current_num = int(f.read())

        print("排隊搶票了")
        time.sleep(1)
        current_num -= 1

        with open('f1.txt', mode='w', encoding='utf-8') as f:
            f.write(str(current_num))


if __name__ == '__main__':
    pool = ProcessPoolExecutor()
    # lock_object = multiprocessing.RLock() # 不能使用
    manager = multiprocessing.Manager()
    lock_object = manager.RLock() # Lock
    for i in range(10):
        pool.submit(task, lock_object)

案例:計算每天使用者訪問情況。

image

  • 示例1

    import os
    import time
    from concurrent.futures import ProcessPoolExecutor
    from multiprocessing import Manager
    
    
    def task(file_name, count_dict):
        ip_set = set()
        total_count = 0
        ip_count = 0
        file_path = os.path.join("files", file_name)
        file_object = open(file_path, mode='r', encoding='utf-8')
        for line in file_object:
            if not line.strip():
                continue
            user_ip = line.split(" - -", maxsplit=1)[0].split(",")[0]
            total_count += 1
            if user_ip in ip_set:
                continue
            ip_count += 1
            ip_set.add(user_ip)
        count_dict[file_name] = {"total": total_count, 'ip': ip_count}
        time.sleep(1)
    
    
    def run():
        # 根據目錄讀取檔案並初始化字典
        """
            1.讀取目錄下所有的檔案,每個程序處理一個檔案。
        """
    
        pool = ProcessPoolExecutor(4)
        with Manager() as manager:
            """
            count_dict={
            	"20210322.log":{"total":10000,'ip':800},
            }
            """
            count_dict = manager.dict()
            
            for file_name in os.listdir("files"):
                pool.submit(task, file_name, count_dict)
                
                
            pool.shutdown(True)
            for k, v in count_dict.items():
                print(k, v)
    
    
    if __name__ == '__main__':
        run()
    
  • 示例2

    import os
    import time
    from concurrent.futures import ProcessPoolExecutor
    
    
    def task(file_name):
        ip_set = set()
        total_count = 0
        ip_count = 0
        file_path = os.path.join("files", file_name)
        file_object = open(file_path, mode='r', encoding='utf-8')
        for line in file_object:
            if not line.strip():
                continue
            user_ip = line.split(" - -", maxsplit=1)[0].split(",")[0]
            total_count += 1
            if user_ip in ip_set:
                continue
            ip_count += 1
            ip_set.add(user_ip)
        time.sleep(1)
        return {"total": total_count, 'ip': ip_count}
    
    
    def outer(info, file_name):
        def done(res, *args, **kwargs):
            info[file_name] = res.result()
    
        return done
    
    
    def run():
        # 根據目錄讀取檔案並初始化字典
        """
            1.讀取目錄下所有的檔案,每個程序處理一個檔案。
        """
        info = {}
        
        pool = ProcessPoolExecutor(4)
    
        for file_name in os.listdir("files"):
            fur = pool.submit(task, file_name)
            fur.add_done_callback(  outer(info, file_name)  ) # 回撥函式:主程序
    
        pool.shutdown(True)
        for k, v in info.items():
            print(k, v)
    
    
    if __name__ == '__main__':
        run()
    
    

5. 協程

暫時以瞭解為主。

計算機中提供了:執行緒、程序 用於實現併發程式設計(真實存在)。

協程(Coroutine),是程式設計師透過程式碼搞出來的一個東西(非真實存在)。

協程也可以被稱為微執行緒,是一種使用者態內的上下文切換技術。
簡而言之,其實就是透過一個執行緒實現程式碼塊相互切換執行(來回跳著執行)。

例如:

def func1():
    print(1)
    ...
    print(2)
    
def func2():
    print(3)
    ...
    print(4)
    
func1()
func2()

上述程式碼是普通的函式定義和執行,按流程分別執行兩個函式中的程式碼,並先後會輸出:1、2、3、4

但如果介入協程技術那麼就可以實現函式見程式碼切換執行,最終輸入:1、3、2、4

在Python中有多種方式可以實現協程,例如:

  • greenlet

    pip install greenlet
    
    from greenlet import greenlet
    
    def func1():
        print(1)        # 第1步:輸出 1
        gr2.switch()    # 第3步:切換到 func2 函式
        print(2)        # 第6步:輸出 2
        gr2.switch()    # 第7步:切換到 func2 函式,從上一次執行的位置繼續向後執行
        
    def func2():
        print(3)        # 第4步:輸出 3
        gr1.switch()    # 第5步:切換到 func1 函式,從上一次執行的位置繼續向後執行
        print(4)        # 第8步:輸出 4
        
    gr1 = greenlet(func1)
    gr2 = greenlet(func2)
    
    gr1.switch() # 第1步:去執行 func1 函式
    
  • yield

    def func1():
        yield 1
        yield from func2()
        yield 2
        
    def func2():
        yield 3
        yield 4
        
    f1 = func1()
    for item in f1:
        print(item)
    

雖然上述兩種都實現了協程,但這種編寫程式碼的方式沒啥意義。

這種來回切換執行,可能反倒讓程式的執行速度更慢了(相比較於序列)。

協程如何才能更有意義呢?

不要讓使用者手動去切換,而是遇到IO操作時能自動切換。

Python在3.4之後推出了asyncio模組 + Python3.5推出async、async語法 ,內部基於協程並且遇到IO請求自動化切換。

import asyncio

async def func1():
    print(1)
    await asyncio.sleep(2)
    print(2)
    
async def func2():
    print(3)
    await asyncio.sleep(2)
    print(4)
    
tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
"""
需要先安裝:pip3 install aiohttp
"""

import aiohttp
import asyncio

async def fetch(session, url):
    print("傳送請求:", url)
    async with session.get(url, verify_ssl=False) as response:
        content = await response.content.read()
        file_name = url.rsplit('_')[-1]
        with open(file_name, mode='wb') as file_object:
            file_object.write(content)
            
async def main():
    async with aiohttp.ClientSession() as session:
        url_list = [
            'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
            'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
            'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
        ]
        tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
        await asyncio.wait(tasks)
if __name__ == '__main__':
    asyncio.run(main())

透過上述內容發現,在處理IO請求時,協程透過一個執行緒就可以實現併發的操作。

協程、執行緒、程序的區別?

執行緒,是計算機中可以被cpu排程的最小單元。
程序,是計算機資源分配的最小單元(程序為執行緒提供資源)。
一個程序中可以有多個執行緒,同一個程序中的執行緒可以共享此程序中的資源。

由於CPython中GIL的存在:
	- 執行緒,適用於IO密集型操作。
    - 程序,適用於計算密集型操作。

協程,協程也可以被稱為微執行緒,是一種使用者態內的上下文切換技術,在開發中結合遇到IO自動切換,就可以透過一個執行緒實現併發操作。


所以,在處理IO操作時,協程比執行緒更加節省開銷(協程的開發難度大一些)。

現在很多Python中的框架都在支援協程,比如:FastAPI、Tornado、Sanic、Django 3、aiohttp等,企業開發使用的也越來越多(目前不是特別多)。

總結

  1. 瞭解的程序的幾種模式
  2. 掌握程序和程序池的常見操作
  3. 程序之間資料共享
  4. 程序鎖
  5. 協程、程序、執行緒的區別(概念)

相關文章