線程、進程、協程

進程是資源分配單位,系統會分配內存,屏幕,窗口。
線程是進程中真正執行的東西。

 python中的thread模塊是比較底層的模塊,python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用。

1、多線程執行

#coding=utf-8
import threading
import time

def saySorry():
print(“親愛的,我錯了,我能吃飯了嗎?”)
time.sleep(1)

if name == “main“:
for i in range(5):
t = threading.Thread(target=saySorry)
t.start() #啓動線程,即讓線程開始執行
2、自定義線程類

coding=utf-8

import threading
import time

class MyThread(threading.Thread):
def init(self, name1, age):
super(MyThread, self).init()
self.name1 = name1
self.age = age

def run(self):
    for i in range(3):
        time.sleep(1)
        msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
        print(msg)

if name == ‘main‘:
t = MyThread()
t.start()

  python的threading.Thread類有一個run方法,用於定義線程的功能函數,可以在自己的線程類中覆蓋該方法。而創建自己的線程實例後,通過Thread類的start方法,可以啓動該線程,交給python虛擬機進行調度,當該線程獲得執行的機會時,就會調用run方法執行線程。



  多線程之間共享全局變量,優點是方便在多個線程之間共享數據,缺點是線程是對全局變量隨意遂改可能造成多線程之間對全局變量的混亂(即線程非安全)。

  如果多個線程同時對同一個全局變量操作,會出現資源競爭問題,從而數據結果會不正確:

import threading
import time

g_num = 0

def work1(num):
global g_num
for i in range(num):
g_num += 1
print(“—-in work1, g_num is %d—”%g_num)

def work2(num):
global g_num
for i in range(num):
g_num += 1
print(“—-in work2, g_num is %d—”%g_num)

print(“—線程創建之前g_num is %d—”%g_num)

t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

while len(threading.enumerate()) != 1:
time.sleep(1)

print(“2個線程對同一個全局變量操作之後的最終結果是:%s” % g_num)

測試結果:
—線程創建之前g_num is 0—
—-in work1, g_num is 1088005—
—-in work2, g_num is 1286202—
2個線程對同一個全局變量操作之後的最終結果是:1286202
同步:

同步就是協同步調,按預定的先後次序進行運行

互斥鎖:

某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。

寫法:

創建鎖

mutex = threading.Lock()

鎖定

mutex.acquire()

釋放

mutex.release()

鎖的好處:

確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行
鎖的壞處:
阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了
由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖

死鎖解決辦法(

看門狗思想:過一一段時間就執行一次特殊的某行代碼,如果長時間不執行,系統就自動重啓

2.銀行家算法

互斥鎖:線程能夠同步保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲鎖定狀態,其他線程不能更改,直到該線程釋放資源。

當創建一個線程之後,函數裏面所有的內存空間是這個線程獨有的,在創建一個時,會重新創建一個內存空間。各人是各人的。函數裏面的代碼各人是各人的,不會共享。

非全局變量不需要加鎖。

死鎖:在線程間共享多個資源的時候,如果兩個線程分別佔有一部分資源並且同時等待對方的資源時,就會造成死鎖。

同步:同步就是協調步調,按照預定的先後次序進行運行 三把鎖一環扣一環
異步:不同步

生產者與消費者模式:
1。隊列 :進程中的隊列和線程中的隊列不是一個概念。隊列就是用來給生產者和消費者解耦的。
2。棧
fifo: frist in frist out =>Queue
filo: frist in last out =>

一個函數想得到另一個函數的值,要麼return 返回值,要麼通過全局變量。

1、使用全局字典的方法
2、ThreadLocal:不用傳參數,用一個全局變量,能過完成線程裏邊的所有的數據的傳遞,不會因爲多個線程對參數的修改對程序產生影響。

孤兒進程:父進程先結束,子進程還沒結束
殭屍進程:如果一個子進程死了,父進程沒有收屍,在收屍前的整個期間,子進程就稱爲殭屍進程。

線程之間共享全局變量。

原子操作(原子性):要麼不做,要做就做完。

線程安全問題:可能在一句代碼還沒執行完,操作系統就停止了代碼的運行。

輪詢:是一種CPU決策如何提供周邊設備服務的方式,又稱程控輸出入

多任務UDP聊天器:

import socket
import threading

定義發送數據的函數

def send_data(udp_socket):

# 定義要發送的內容
send_content = input("請輸入要發送的內容:")

# 請輸入IP地址
ipddr = input("請輸入IP地址,格式爲:xxx.xxx.xxx.xxx :")

# 請輸入端口號
port = int(input("請輸入端口號:"))

# 把要發送的數據轉換爲 二進制
send_data = send_content.encode("utf-8")

# 發送數據
udp_socket.sendto(send_data, (ipddr, port))
# 測試

定義接收數據的函數

def recvData(udp_socket):
while True:
# 接收數據
recv_data = udp_socket.recvfrom(1024)
# 如果數據存在,則解析數據
if recv_data:
# 拆包,得到內容
msg, list_port = recv_data
# 拆包,得到msg
msg = msg.decode(“gbk”)
# 打印內容
print(msg, list_port)
else:
# 數據不存在停止循環
break

定義主入口函數

def main():

# 定義套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定服務器客戶端端口
# udp_socket.bind(("", 7878))

# 定義子線程並且開啓
t1 = threading.Thread(target=recvData, args=(udp_socket, ))
# 設置守護線程
t1.setDaemon(True)
t1.start()

while True:

    print("-------------------")
    print("--   1、發送數據   --")
    print("--   2、退出系統   --")
    print("-------------------")
    num = int(input("請選擇功能【1/2/3】:"))

    if num < 1 or num > 3:
        print("輸入不合法!,請重新輸入")
    else:
        if num == 1:
            # 發送數據
            send_data(udp_socket)

        if num == 2:
            print("正在退出系統...")
            print("系統已退出")
            break

if name == ‘main‘:

main()

進程:操作系統中的算法包括:時間片輪轉、優先級調度、

併發:看上去一起執行。當前的任務數量大於核數。
並行:真正的一起執行。當前任務數小於核數。

調度算法:什麼樣的情況下按照什麼樣的規則讓誰去執行。
編寫完畢的代碼,在沒有運行的時候稱之爲程序,在運行的時候稱之爲進程。

1、fork()創造子線程
import os
fork():可以在python程序中創建子進程。
ret = os.fork()
在fork()中,主進程想要結束,不會因爲子進程沒有結束而等待。只要子進程產生,子進程的執行順序和執行過程和主進程一樣,就是衆所周知的代碼執行的過程。

2.pid值:
getpid():獲取當前進程的pid值。
pid值:在操作系統當中,當進程運行起來時,操作系統都會給這個進程分配一個獨一無二的值,即pid值。processID
父進程中fork的返回值,就是剛剛創建出來的子進程的id。
getppid():獲取父進程的pid值。
pid值小於等於65535

3.Process()創造子線程:

import multiprocessing
import time

定義函數

def work1():
for i in range(10):
print(“work1—-“, i)
time.sleep(0.5)

if name == ‘main‘:

# 創建進程
# 1. 導入 multiprocessing 模塊
# 2. multiprocessing.Process() 創建子進程
# 3. start() 方法啓動進程

p1 = multiprocessing.Process(group=None, target=work1)
p1.start()

for i in range(10):
    print("這是主進程", i)
    time.sleep(0.5)




p.join()#加了join之後,主進程會等子進程執行完代碼之後,再開始執行join下面的代碼

join([timeout])#堵塞:主進程等待子進程結束之後才結束。timeout表示操作時間。
terminate():不管任務是否完成,立即終止。

由於process的跨平臺更好,以後不用fork,而是用process

Process語法結構如下:

Process([group [, target [, name [, args [, kwargs]]]]])
target:如果傳遞了函數的引用,可以任務這個子進程就執行這裏的代碼
args:給target指定的函數傳遞的參數,以元組的方式傳遞
kwargs:給target指定的函數傳遞命名參數
name:給進程設定一個名字,可以不設定

group:指定進程組,大多數情況下用不到

Process創建的實例對象的常用方法:
start():啓動子進程實例(創建子進程)
is_alive():判斷進程子進程是否還在活着
join([timeout]):是否等待子進程執行結束,或等待多少秒

terminate():不管任務是否完成,立即終止子進程

Process創建的實例對象的常用屬性:
name:當前進程的別名,默認爲Process-N,N爲從1開始遞增的整數

pid:當前進程的pid(進程號)

進程間不同享全局變量

進程線程對比:

進程,能夠完成多任務,比如 在一臺電腦上能夠同時運行多個QQ

線程,能夠完成多任務,比如 一個QQ中的多個聊天窗口

進程是系統進行資源分配和調度的一個獨立單位.

線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.
區別:
一個程序至少有一個進程,一個進程至少有一個線程.
線程的劃分尺度小於進程(資源比進程少),使得多線程程序的併發性高。
進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率
線線程不能夠獨立執行,必須依存在進程中
可以將進程理解爲工廠中的一條流水線,而其中的線程就是這個流水線上的工人
優缺點:

線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。

4.進程池Pool創建子線程

import multiprocessing
import time

def copy_work():
print(“拷貝中….”,multiprocessing.current_process().pid)
time.sleep(0.3)

if name == ‘main‘:

# 創建進程池
# Pool(3) 表示創建容量爲3個進程的進程池
pool = multiprocessing.Pool(3)

for i in range(10):
    # 利用進程池同步拷貝文件,進程池中的進程會必須等上一個進程退出才能執行下一個進程
    # pool.apply(copy_work)
    pool.apply_async(copy_work)

pool.close()
# 注意:如果使用異步方式執行copy_work任務,主線程不再等待子線程執行完畢再退出!
pool.join()

進程池Pool:主進程一般用來等待,真正的任務都在子進程中執行。

multiprocessing.Pool常用函數解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式調用func(並行執行,堵塞方式必須等待上一個進程退出才能執行下一個進程),args爲傳遞給func的參數列表,kwds爲傳遞給func的關鍵字參數列表;
close():關閉Pool,使其不再接受新的任務;
terminate():不管任務是否完成,立即終止;

join():主進程阻塞,等待子進程的退出, 必須在close或terminate之後使用;

進程池中的Queue
如果要使用Pool創建進程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否則會得到一條如下的錯誤信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.

下面的實例演示了進程池中的進程如何通信:

import multiprocessing
import time

def write_queue(queue):
# 循環寫入數據
for i in range(10):

    if queue.full():
        print("隊列已滿!")
        break

    # 向隊列中放入消息
    queue.put(i)

    time.sleep(0.5)

def read_queue(queue):
# 循環讀取隊列消息
while True:
# 隊列爲空,停止讀取
if queue.empty():
print(“—隊列已空—”)
break

    # 讀取消息並輸出
    result = queue.get()
    print(result)

if name == ‘main‘:

# 創建消息隊列
queue = multiprocessing.Queue(3)

# 創建子進程
p1 = multiprocessing.Process(target=write_queue, args=(queue,))
p1.start()
# 等待p1寫數據進程執行結束後,再往下執行
p1.join()

p1 = multiprocessing.Process(target=read_queue, args=(queue,))
p1.start()

q.put():存數據 q.get():取數據 q.full():判斷數據是否是滿的 q.empty()判斷數據是否爲空 q.get_nowait():立即存數據不等待 q.put_nowait():立即取數據不等待

進程間通信的方式:命名管道 無名管道 共享內存 隊列 網絡功能

fork ()是最底層的方法。
pool = Pool(3)
pool.apply_async(xx)
pool 中,主進程一般不幹活,主要是創建的子進程幹活,join()方法用來等待。

apply()=>堵塞式

進程共享數據,寫實拷貝。

主進程的pid 是運行程序的那個軟件的pid值

案例:文件夾copy器

import multiprocessing
import os

file_name 文件名

source_dir 源文件目錄

dest_dir 目標文件目錄

def copy_work(file_name, source_dir, dest_dir):

# 拼接路徑
source_path = source_dir+"/"+file_name

dest_path = dest_dir+"/"+file_name

print(source_path, "----->", dest_path)
# 打開源文件、創建目標文件
with open(source_path,"rb") as source_file:
    with open(dest_path,"wb") as dest_file:
        while True:
            # 循環讀取數據
            file_data = source_file.read(1024)
            if file_data:
                # 循環寫入到目標文件
                dest_file.write(file_data)
            else:
                break

if name == ‘main‘:

# 1、定義源文件目錄和目標文件夾的目錄
source_dir = "test"
dest_dir = "/home/teahcer/桌面/test"

try:
    # 2、創建目標文件夾目錄
    os.mkdir(dest_dir)

except:
    print("目標文件夾已經存在,未創建~")

# 3、列表得到所有的源文件中的文件
file_list = os.listdir(source_dir)
print(file_list)

# 4、創建進程池
pool = multiprocessing.Pool(3)
# 5、for 循環,依次拷貝每個文件
for file_name in file_list:
    # copy_work(file_name, source_dir, dest_dir)
    pool.apply_async(copy_work, args=(file_name, source_dir, dest_dir))
# 6、關閉進程池
pool.close()
# 7、設置主進程等待子進程執行結束再退出

pool.join()

協程:

迭代器(iterator):

迭代是訪問集合元素的一種方式。

迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。

迭代器只能往前不會後退。

可以對list、tuple、str等類型的數據使用for…in…的循環語法從其中依次拿到數據進行使用,這樣的過程稱爲遍歷,也叫迭代。

可迭代對象(Iterable):可以通過for…in…這類語句迭代讀取一條數據供我們使用的對象。列表元組字典都是 可迭代對象。

可迭代對象通過iter方法向我們提供一個迭代器,我們在迭代一個可迭代對象的時候,實際上就是先獲取該對象提供的一個迭代器,然後通過這個迭代器來依次獲取對象中的每一個數據.

一個具備了iter方法的對象,就是一個可迭代對象。

一個實現了iter方法和next方法的對象,就是迭代器。

iter()函數與next()函數
list、tuple等都是可迭代對象,我們可以通過iter()函數獲取這些可迭代對象的迭代器。然後我們可以對獲取到的迭代器不斷使用next()函數來獲取下一條數據。iter()函數實際上就是調用了可迭代對象的iter方法。

li = [11, 22, 33, 44, 55]

li_iter = iter(li)

next(li_iter)

11

next(li_iter)

22

next(li_iter)

33

next(li_iter)

44

next(li_iter)

55

next(li_iter)

Traceback (most recent call last):

File “”, line 1, in

StopIteration

>
可以使用 isinstance() 判斷一個對象是否是 Iterator 對象.

一個實現了iter方法和next方法的對象,就是迭代器。

自定義迭代器

from collections import Iterable
from collections import Iterator

class StudentList(object):

def __init__(self):
    # 創建列表
    self.items = list()

def addItem(self,item):
    # 追加元素到列表中
    self.items.append(item)

def __iter__(self):

    # 創建迭代器對象
    studentIterator = StudentIterator(self.items)

    # 返回迭代器對象
    return studentIterator

定義迭代器

class StudentIterator(object):
# 定義構造方法
# 1)完成 索引下標定義和初始化
# 2)接收要遍歷的列表值

def __init__(self, items):

    self.items = items
    self.current_index = 0

def __iter__(self):
    return self

def __next__(self):
    # 判斷位置是否合法
    if self.current_index < len(self.items):
        # 根據current_index 返回列表值
        item = self.items[self.current_index]
        # 讓 下標+1
        self.current_index += 1
        # 返回元素內容
        return item

    else:
        # 停止迭代
        # 主動拋出異常,迭代器沒有更多的值(到了迭代器末尾)
        raise StopIteration

實例化對象

stulist = StudentList()

stulist.addItem(“張三”)
stulist.addItem(“李四”)
stulist.addItem(“C羅”)

 檢查是否是可迭代對象

result = isinstance(stulist, Iterable)
print(result)

for value in stulist:
print(value)
for…in…循環的本質
for item in Iterable 循環的本質就是先通過iter()函數獲取可迭代對象Iterable的迭代器,然後對獲取到的迭代器不斷調用next()方法來獲取下一個值並將其賦值給item,當遇到StopIteration的異常後循環結束。

  1. 迭代器的應用場景

我們發現迭代器最核心的功能就是可以通過next()函數的調用來返回下一個數據值。如果每次返回的數據值不是在一個已有的數據集合中讀取的,而是通過程序按照一定的規律計算生成的,那麼也就意味着可以不用再依賴一個已有的數據集合,也就是說不用再將所有要迭代的數據都一次性緩存下來供後續依次讀取,這樣可以節省大量的存儲(內存)空間。

斐波那契數:

class Fibonacci():

def __init__(self, num):
    # 通過構造方法,保存num到類的成員屬性中
    self.num = num
    # 定義變量保存斐波那契數列前兩個值
    self.a = 0
    self.b = 1

    # 記錄當前的變量值
    self.current_index = 0

def __iter__(self):
    # 返回迭代器,因自身就是迭代器,故可以返回自己
    return self

def __next__(self):

    # 判斷是否生成完畢
    if self.current_index < self.num:
        # 返回
        result = self.a

        # 交換兩個變量值
        self.a, self.b = self.b, self.a+self.b

        self.current_index += 1

        return result

    else:
        # 停止迭代
        raise StopIteration

if name == ‘main‘:
# 創建迭代器
fib_iterator = Fibonacci(5)

# 使用迭代器,輸出斐波那契數列值
for value in fib_iterator:
    print(value, end=" ")

並不是只有for循環能接收可迭代對象,除了for循環能接收可迭代對象,list、tuple等也能接收。

li = list(FibIterator(15))

print(li)

tp = tuple(FibIterator(6))

print(tp)
生成器:

利用迭代器,我們可以在每次迭代獲取數據(通過next()方法)時按照特定的規律進行生成。但是我們在實現一個迭代器時,關於當前迭代到的狀態需要我們自己記錄,進而才能根據當前狀態生成下一個數據。爲了達到記錄當前狀態,並配合next()函數進行迭代使用,我們可以採用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器。
創建生成器方法1
要創建一個生成器,有很多種方法。第一種方法很簡單,只要把一個列表生成式的 [ ] 改成 ( )。

In [15]: L = [ x*2 for x in range(5)]

In [16]: L
Out[16]: [0, 2, 4, 6, 8]

In [17]: G = ( x*2 for x in range(5))

In [18]: G
Out[18]:

coding=utf-8

from greenlet import greenlet
import time

def test1():
while True:
print “—A–”
gr2.switch()
time.sleep(0.5)

def test2():
while True:
print “—B–”
gr1.switch()
time.sleep(0.5)

gr1 = greenlet(test1)
gr2 = greenlet(test2)

切換到gr1中運行

gr1.switch()
用gevent實現協程:
gevent比greenlet更厲害,直不用調用switch方法,可以自動切換任務。
gevent的原理:當一個greenlet遇到IO(指的是input output 輸入輸出,比如網絡、文件操作等)操作時,比如訪問網絡,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。

由於IO操作非常耗時,經常使程序處於等待狀態,有了gevent爲我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。

安裝:pip install gevent

import time
import gevent

def work1():
for i in range(5):
print(“work1 —–1”)
time.sleep(0.5)

def work2():
for i in range(5):
print(“work2 —–2”)
time.sleep(0.5)

創建攜程並指派任務

g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)

等待協程執行完成再關閉主線程

g1.join()
g2.join()
注意:上面代碼中的time方法用的是gevent包中的。

用gevent時,只要加入堵塞的方法都需要gevent裏面的,所有的延時堵塞方法都要用gevent裏面的。

這樣使用時就不是很方便,可能會不知道哪些方法需要用gevent裏面的 。因此需要導入monkey。並在代碼的最前面寫一句:

monkey .patch_all()#相當於monkey把代碼中的所有延時操作都改成用gevent裏面的。

協程併發下載器:

導入urllib模塊

import urllib.request
import gevent

def download_img(img_url, filename):
try:
# 打開url
response = urllib.request.urlopen(img_url)
# 創建文件
with open(filename, “wb”) as img_file:
# 通過循環不斷讀取數據
while True:
# 將讀取到的數據保存到變量中
img_data = response.read(1024)
# 如果讀取成功,則寫數據到文件中
if img_data:
# 寫數據
img_file.write(img_data)
else:
break
except Exception as e:
print(“下載圖片出現錯誤~!”,e)
else:
print(“圖片 %s 下載完成!” % filename)

def main():
# 定義變量保存要下載的圖片地址
img_url1 = “http://img.mp.itc.cn/upload/20170716/8e1b835f198242caa85034f6391bc27f.jpg
img_url2 = “http://pic1.wed114.cn/allimg/180227/1023303521-1.gif
img_url3 = “http://image.uczzd.cn/11867042470350090334.gif?id=0&from=export

# 開啓協程 調用下載方法
gevent.joinall([
    gevent.spawn(download_img, img_url1, "1.gif"),
    gevent.spawn(download_img, img_url2, "2.gif"),
    gevent.spawn(download_img, img_url3, "3.gif")
])

主入口

if name == ‘main‘:
main()

進程線程協程之間的區別:

進程是資源分配的單位
線程是操作系統調度的單位
進程切換需要的資源很最大,效率很低
線程切換需要的資源一般,效率一般(當然了在不考慮GIL的情況下)
協程切換任務資源很小,效率高
多進程、多線程根據cpu核數不一樣可能是並行的,但是協程是在一個線程中 所以是併發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章