【第九章】:線程、進程和協程

1、線程

線程是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務html

注:cpu內一個核數只能同時運行一個線程,因此多核cpu同時能夠運行多個線程;可是在Python中,即便是多核cpu,同時運行的線程也只有一個,Python語言設計之初就不支持多核,因此在Python程序中,啓用越多的線程,程序不必定運行的就很快,由於cpu要進行大量的上下文切換,反而消耗時間;GIL全局解釋鎖保障線程的上下文關係,保障當前只有一個線程在運行,與lock數據加鎖無關python

一、threading模塊git

線程建立有2種方式:以下程序員

直接調用github

import  threading,time

def run(n):
    print("test...",n)
    time.sleep(2)

if __name__ == '__main__':
    
    t1 = threading.Thread(target=run,args=("t1",))
    t2 = threading.Thread(target=run,args=("t2",))
    
    # 兩個同時執行,而後等待兩秒程序結束
    t1.start()
    t2.start()

# 程序輸出
# test... t1
# test... t2

繼承式調用編程

import threading,time

class MyThread(threading.Thread):
    def __init__(self,num):
       # threading.Thread.__init__(self)
        super(MyThread,self).__init__()
        self.num =num

    def run(self):#定義每一個線程要運行的函數
        print("running on number:%s" %self.num)
        time.sleep(2)


if __name__ == '__main__':
    # 兩個同時執行,而後等待兩秒程序結束
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

# 程序輸出
# running on number:1
# running on number:2

 

二、join安全

join等待線程執行完後,其餘線程再繼續執行(串行)服務器

import  threading,time

def run(n,sleep_time):
    print("test...",n)
    time.sleep(sleep_time)
    print("test...done", n)
if __name__ == '__main__':

    t1 = threading.Thread(target=run,args=("t1",2))
    t2 = threading.Thread(target=run,args=("t2",3))

    # 兩個同時執行,而後等待t1執行完成後,主線程和子線程再開始執行
    t1.start()
    t2.start()
    t1.join()   # 等待t1

    print("main thread")

# 程序輸出
# test... t1
# test... t2
# test...done t1
# main thread
# test...done t2

 

三、Daemon網絡

Daemon守護進程,主程序執行完畢時,守護線程會同時退出,無論是否執行完任務多線程

import threading,time

def run(n):
    print('[%s]------running----\n' % n)
    time.sleep(2)
    print('--done--')


def main():
    for i in range(5):
        t = threading.Thread(target=run, args=[i, ])
        t.start()
        t.join(1)
        print('starting thread', t.getName())


m = threading.Thread(target=main, args=[])
m.setDaemon(True)  # 將main線程設置爲Daemon線程,它作爲程序主線程的守護線程,當主線程退出時,
                    # m線程也會退出,由m啓動的其它子線程會同時退出,無論是否執行完任務
m.start()
m.join(timeout=2)
print("---main thread done----")

# 程序輸出
# [0]------running----
# starting thread Thread-2
# [1]------running---- 
# --done--
# ---main thread done----

 

四、Mutex  線程鎖(互斥鎖) 

一個進程下能夠啓動多個線程,多個線程共享父進程的內存空間,也就意味着每一個線程能夠訪問同一份數據,此時,若是2個線程同時要修改同一份數據,會出現什麼情況?

import time
import threading


def addNum():
    global num  # 在每一個線程中都獲取這個全局變量
    print('--get num:', num)
    time.sleep(1)
    num -= 1  # 對此公共變量進行-1操做


num = 100  # 設定一個共享變量
thread_list = []
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待全部線程執行完畢
    t.join()

print('final num:', num)

正常來說,這個num結果應該是0, 但在python 2.7上多運行幾回,會發現,最後打印出來的num結果不老是0,爲何每次運行的結果不同呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操做, 因爲2個線程是併發同時運行的,因此2個線程頗有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再賦值給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每一個線程在要修改公共數據時,爲了不本身在還沒改完的時候別人也來修改此數據,能夠給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此數據。 

*注:不要在3.x上運行,不知爲何,3.x上的結果老是正確的,多是自動加了鎖

 對程序進行加鎖

import time
import threading


def addNum():
    global num  # 在每一個線程中都獲取這個全局變量
    print('--get num:', num)
    time.sleep(1)
    lock.acquire()  # 修改數據前加鎖
    num -= 1  # 對此公共變量進行-1操做
    lock.release()  # 修改後釋放


num = 100  # 設定一個共享變量
thread_list = []
lock = threading.Lock()  # 生成全局鎖
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待全部線程執行完畢
    t.join()

print('final num:', num)

 機智的同窗可能會問到這個問題,就是既然你以前說過了,Python已經有一個GIL來保證同一時間只能有一個線程來執行了,爲何這裏還須要lock? 注意啦,這裏的lock是用戶級的lock,跟那個GIL不要緊 ,具體咱們經過下圖來看一下

 

 

五、RLock 遞歸鎖

當一個大鎖中還要再包含子鎖的時候,若是再用threading.Lock的話,程序鎖和鑰匙會出現對不上的狀況,這時候就須要用到遞歸鎖

import threading, time


def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num += 1
    lock.release()
    return num


def run2():
    print("grab the second part data")
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2


def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res, res2)


if __name__ == '__main__':

    num, num2 = 0, 0
    lock = threading.RLock()
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()

while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num, num2)

遞歸鎖實現原理其實很簡單,只是把每次鎖和鑰匙對應記錄起來,就不是出現鎖死的狀況

 

六、Semaphore 信號量 

Mutex 同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 ,好比廁全部3個坑,那最多隻容許3我的上廁所,後面的人只能等裏面有人出來了才能再進去。

import threading, time


def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" % n)
    semaphore.release()


if __name__ == '__main__':

    num = 0
    semaphore = threading.BoundedSemaphore(5)  # 最多容許5個線程同時運行
    for i in range(20):
        t = threading.Thread(target=run, args=(i,))
        t.start()

while threading.active_count() != 1:
    pass  # print threading.active_count()
else:
    print('----all threads done---')
    print(num)

實現效果,每次同時執行5個線程  

 

七、Event

經過Event來實現兩個或多個線程間的交互,下面是一個紅綠燈的例子,即起動一個線程作交通指揮燈,生成幾個線程作車輛,車輛行駛按紅燈停,綠燈行的規則。

import threading,time

def light():
    count = 0
    while True:
        if count < 10:      #紅燈
            print("\033[41;1m紅燈\033[0m",10-count)
        elif count >= 10 and count < 30:    #綠燈
            event.set() # 設置標誌位
            print("\033[42;1m綠燈\033[0m",30-count)
        else:
            event.clear() #把標誌位清空
            count = 0
        time.sleep(1)
        count +=1

def car(n):
    while True:
        if event.is_set():
            print("\033[32;0m[%s]在路上飛奔.....\033[0m"%n)
        else:
            print("\033[31;0m[%s]等紅燈等的花都謝了.....\033[0m" % n)
        time.sleep(1)

if __name__ == "__main__":
    event = threading.Event()
    light = threading.Thread(target=light)
    light.start()
    car = threading.Thread(target=car,args=("tesla",))
    car.start()

 

八、queue 

實現解耦、隊列;先進先出,後進後出(當get不到數據時,會一直卡着等待數據)

import queue


q = queue.Queue()
for i in range(10):
    q.put(i)

for t in range(10):
    print(q.get())
    
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

class queue.Queue(maxsize=0) #先入先出
class queue.LifoQueue(maxsize=0) #last in fisrt out 
class queue.PriorityQueue(maxsize=0) #存儲數據時可設置優先級的隊列
Constructor for a priority queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.

The lowest valued entries are retrieved first (the lowest valued entry is the one returned by sorted(list(entries))[0]). A typical pattern for entries is a tuple in the form: (priority_number, data).

exception queue.Empty
Exception raised when non-blocking get() (or get_nowait()) is called on a Queue object which is empty.

exception queue.Full
Exception raised when non-blocking put() (or put_nowait()) is called on a Queue object which is full.

Queue.qsize()
Queue.empty() #return True if empty  
Queue.full() # return True if full 
Queue.put(item, block=True, timeout=None)
Put item into the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case).

Queue.put_nowait(item)
Equivalent to put(item, False).

Queue.get(block=True, timeout=None)
Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise the Empty exception (timeout is ignored in that case).

Queue.get_nowait()
Equivalent to get(False).

Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.

Queue.task_done()
Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.

If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).

Raises a ValueError if called more times than there were items placed in the queue.

Queue.join() block直到queue被消費完畢
更多..

python多線程,不適合cpu密集操做型的任務,適合io操做密集型的任何;

補充:

獲取線程ID的方式(python的threading由於封裝的太好, 不少本源的東西在threading對象裏是拿不到的.  首先須要說明的是python threading的name跟ident,這些看起來是線程名字,線程id其實只是個標識,注意是標識而已.  簡單過了下threading建立對象及啓動線程的代碼,發現ident跟pstree查到的線程id是兩碼事. )

import time
import threading
import ctypes

print(threading.currentThread())
print(threading.currentThread().ident)
print(ctypes.CDLL('libc.so.6').syscall(186))

# <_MainThread(MainThread, started 139645800499008)>
# 139645800499008
# 74856

  

 

 

 

2、進程

要以一個總體的形式暴露給操做系統管理,裏面包含了對各類資源的調用,內存的管理,網絡接口的調用等;對各類資源的管理集合,就能夠稱爲進程

一、multiprocessing模塊(多進程

multiprocessing與threading使用方法相似,下面建立個程序看看

#多進程

import multiprocessing,time
import threading

def thread_run():
    print("thread id ",threading.get_ident())

def run(name):
    time.sleep(1)
    print("process----",name)
    t = threading.Thread(target=thread_run,)
    t.start()

if __name__ == "__main__":

    for i in range(10):
        p = multiprocessing.Process(target=run,args=("lzl",))
        p.start()

作一個程序,對比下主進程和子進程的id號以及關係

#多進程id

from multiprocessing import Process
import os

def info(title):
    print(title)
    print('module name:', __name__)
    print('parent process:', os.getppid())  # 父進程id
    print('process id:', os.getpid())       # 子進程id

def f(name):
    info('\033[31;1mfunction f\033[0m')
    print('hello', name)

if __name__ == '__main__':
    info('\033[32;1mmain process line\033[0m')
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

# 輸出
# main process line
# module name: __main__
# parent process: 7668
# process id: 7496
# function f
# module name: __mp_main__
# parent process: 7496
# process id: 7188
# hello bob

 

二、進程間通訊

不一樣進程間內存是不共享的,要想實現兩個進程間的數據交換,能夠用如下方法:

 Queue

Queue使用方法跟threading裏的queue差很少

#Queue 進程間通訊

import multiprocessing

def f(q):
    q.put([42,None,"hello"])

if __name__ == "__main__":
    q = multiprocessing.Queue()
    p = multiprocessing.Process(target=f,args=(q,))
    p.start()
    print(q.get())
    p.join()

#輸出 [42, None, 'hello']

Pipe

#Pipe 進程間通訊

import multiprocessing

def f(conn):
    conn.send("hello from child")
    conn.close()

    pass

if __name__ == "__main__":
    parent_conn,child_conn = multiprocessing.Pipe()
    p = multiprocessing.Process(target=f,args=(child_conn,))
    p.start()
    print(parent_conn.recv())
    p.join()


#輸出 hello from child

Queue和Pipe實際上實現的是進程間的數據傳遞,並無在進程間共享數據,若是要共享數據的話,得用到下面的Manager

③ Manager

#Manager 進程間共享數據

import multiprocessing
import os

def f(d,l):
    d["1"] = 1
    d["2"] = 2
    l.append(os.getpid())


if __name__ == "__main__":
    manager = multiprocessing.Manager()
    d = manager.dict()   #建立一個字典,進程間能夠共享數據
    l = manager.list()
    p_list = []
    for i in range(10):
        p = multiprocessing.Process(target=f,args=(d,l,))
        p.start()
        p_list.append(p)
    for t in p_list:
        t.join()

    print(d)
    print(l)

#輸出
# {'2': 2, '1': 1}
# [516, 3628, 6076, 5020, 5396, 4752, 6072, 3608, 3704, 5124]

進程同步

 Without using the lock output from the different processes is liable to get all mixed up

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    print('hello world', i)
    l.release()

if __name__ == '__main__':
    lock = Lock()
    for num in range(10):
        Process(target=f, args=(lock, num)).start()

 

三、進程池

進程建立子進程的過程,子進程克隆了一遍父進程裏的數據,若是父進程佔用空間特別大,子進程啓動過多就會致使系統空間不夠用,因此引出了進程池的概念;進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,若是進程池序列中沒有可供使用的進進程,那麼程序就會等待,直到進程池中有可用進程爲止。

進程池中有兩個方法:

  • apply  同步執行(串行)
  • apply_async  異步執行(並行)
# 進程池

from  multiprocessing import Process, Pool
import time,os

def Foo(i):
    time.sleep(2)
    print("in process",os.getpid())
    return i + 100

def Bar(arg):
    print('-->exec done:',arg,os.getpid())

if __name__ == "__main__":
    pool = Pool(5)      #容許進程池同時放入5個進程
    print("主進程:",os.getpid())
    for i in range(10):
        #pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback回調 執行完func後再執行callback 用主程序執行
        pool.apply(func=Foo, args=(i,))

    pool.close()
    pool.join()  # 進程池中進程執行完畢後再關閉,若是註釋,那麼程序直接關閉。

# 主進程: 5896
# in process 1520
# in process 5596
# -->exec done: 102 5896
# in process 3384
# -->exec done: 100 5896
# -->exec done: 101 5896
# in process 6112
# -->exec done: 103 5896
# in process 1472
# -->exec done: 104 5896
# in process 1520
# in process 5596
# -->exec done: 106 5896
# -->exec done: 105 5896
# in process 3384
# -->exec done: 107 5896
# in process 6112
# -->exec done: 108 5896
# in process 1472
# -->exec done: 109 5896

最近有更新-》》http://www.cnblogs.com/lianzhilei/p/7009826.html

 

3、協程

協程,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程

協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以:協程能保留上一次調用時的狀態(即全部局部狀態的一個特定組合),每次過程重入時,就至關於進入上一次調用的狀態,換種說法:進入上一次離開時所處邏輯流的位置。

好處:

  • 無需線程上下文切換的開銷
  • 無需原子操做鎖定及同步的開銷
  • 方便切換控制流,簡化編程模型
  • 高併發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題。因此很適合用於高併發處理。

缺點:

  • 沒法利用多核資源:協程的本質是個單線程,它不能同時將 單個CPU 的多個核用上,協程須要和進程配合才能運行在多CPU上.固然咱們平常所編寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用。
  • 進行阻塞(Blocking)操做(如IO時)會阻塞掉整個程序

一、實例

使用yield實現協程操做例子 ,單線程下實現多併發的效果 

# yield實現協程

def consumer(name):
    print("------>starting eating baozi..")
    while True:
        new_baozi = yield
        print("[%s] is eating baozi %s"%(name,new_baozi))

def producer():
    n = 0
    while n < 5 :
        n +=1
        con.send(n)         #喚醒yield而且傳值
        con2.send(n)
        print("\033[32;1m[producer]\033[0m is making baozi %s" % n)

if __name__ == "__main__":
    con = consumer("c1")        #生成生成器
    con2 = consumer("c2")
    con.__next__()         #喚醒yield
    con2.__next__()
    producer()

# 輸出
# ------>starting eating baozi..
# ------>starting eating baozi..
# [c1] is eating baozi 1
# [c2] is eating baozi 1
# [producer] is making baozi 1
# [c1] is eating baozi 2
# [c2] is eating baozi 2
# [producer] is making baozi 2
# [c1] is eating baozi 3
# [c2] is eating baozi 3
# [producer] is making baozi 3
# [c1] is eating baozi 4
# [c2] is eating baozi 4
# [producer] is making baozi 4
# [c1] is eating baozi 5
# [c2] is eating baozi 5
# [producer] is making baozi 5

協程之因此能夠出來高併發,原理遇到I/O操做就切換,只剩下CPU操做(CPU操做很是快)

 

二、greenlet  

greenlet封裝好的協程,利用.swith對協程操做進行手動切換

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

from greenlet import greenlet

def test1():
    print("in test1 12")
    gr2.switch()
    print("in test1 34")
    gr2.switch()

def test2():
    print("in test2 56")
    gr1.switch()
    print("in test2 78")
gr1 = greenlet(test1)       #啓動一個協程
gr2 = greenlet(test2)
gr1.switch()        #切換操做 相似於yeild裏的next()

# 輸出
# in test1 12
# in test2 56
# in test1 34
# in test2 78

 

三、gevent

Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#-Author-Lian

import gevent

def foo():
    print("runing in foo")
    gevent.sleep(2)
    print("context swith to foo again")

def bar():
    print("context to bar")
    gevent.sleep(1)
    print("context to swith bar to bar")

gevent.joinall([        #啓動協程
    gevent.spawn(foo),
    gevent.spawn(bar),
])

#輸出
# runing in foo
# context to bar
# context to swith bar to bar
# context swith to foo again

還原生成環境下,利用gevent作同步與異步的性能對比

# 同步異步性能對比

import urllib.request
import gevent,time
from gevent import monkey
monkey.patch_all()  #monkey.patch_all()執行後能夠識別urllib裏面的I/0操做

def f(url):
    print("GET: %s"%url)
    resp = urllib.request.urlopen(url)
    data = resp.read()
    print("%d bytes received from %s"%(len(data),url))

# 同步開銷
urls = [
     'https://www.python.org/',
    'https://www.yahoo.com/',
    'https://github.com/',
]
time_start = time.time()
for url in urls:
    f(url)
print("同步cost time",time.time()-time_start)

# 異步開銷
async_time_start = time.time()
gevent.joinall([
    gevent.spawn(f,'https://www.python.org/'),
    gevent.spawn(f,'https://www.yahoo.com/'),
    gevent.spawn(f,'https://github.com/')
])
print("異步cost time",time.time()-async_time_start)

# 輸出
# GET: https://www.python.org/
# 47446 bytes received from https://www.python.org/
# GET: https://www.yahoo.com/
# 431619 bytes received from https://www.yahoo.com/
# GET: https://github.com/
# 25478 bytes received from https://github.com/
# 同步cost time 4.225241661071777
# GET: https://www.python.org/
# GET: https://www.yahoo.com/
# GET: https://github.com/
# 25478 bytes received from https://github.com/
# 461925 bytes received from https://www.yahoo.com/
# 47446 bytes received from https://www.python.org/
# 異步cost time 2.5521459579467773

由上面程序可知,同步開銷時間爲4秒,異步開銷爲2.5秒,大大節省了開銷,這就是協程的魅力;monkey.patch_all()使gevent能識別到urllib中的I/O操做

經過gevent實現單線程下的多socket併發

import sys
import socket
import time
import gevent
 
from gevent import socket,monkey
monkey.patch_all()
 
 
def server(port):
    s = socket.socket()
    s.bind(('0.0.0.0', port))
    s.listen(5)
    while True:
        conn, addr = s.accept()
        gevent.spawn(handle_request, conn)
 
 
 
def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            print("recv:", data)
            conn.send(data)
            if not data:
                conn.shutdown(socket.SHUT_WR)
 
    except Exception as  ex:
        print(ex)
    finally:
        conn.close()
if __name__ == '__main__':
    server(8001)
socket_server
import socket
 
HOST = 'localhost'    # The remote host
PORT = 8001           # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"),encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)
    #print(data)
 
    print('Received', repr(data))
s.close()
socket_client

咱們在這裏留一個問題,如今已經明白了異步的優勢,當遇到I/0操做時會進行切換操做,那麼程序是如何知道以前的I/O執行完畢再切換回來的呢?!

 

 

4、論事件驅動與異步IO

一般,咱們寫服務器處理模型的程序時,有如下幾種模型:

(1)每收到一個請求,建立一個新的進程,來處理該請求;

(2)每收到一個請求,建立一個新的線程,來處理該請求;

(3)每收到一個請求,放入一個事件列表,讓主進程經過非阻塞I/O方式來處理請求

上面的幾種方式,各有千秋,

第(1)中方法,因爲建立新的進程的開銷比較大,因此,會致使服務器性能比較差,但實現比較簡單。

第(2)種方式,因爲要涉及到線程的同步,有可能會面臨死鎖等問題。

第(3)種方式,在寫應用程序代碼時,邏輯比前面兩種都複雜。

綜合考慮各方面因素,通常廣泛認爲第(3)種方式是大多數網絡服務器採用的方式

看圖說話講事件驅動模型

在UI編程中,經常要對鼠標點擊進行相應,首先如何得到鼠標點擊呢?

方式一:建立一個線程,該線程一直循環檢測是否有鼠標點擊,那麼這個方式有如下幾個缺點:
1. CPU資源浪費,可能鼠標點擊的頻率很是小,可是掃描線程仍是會一直循環檢測,這會形成不少的CPU資源浪費;若是掃描鼠標點擊的接口是阻塞的呢?
2. 若是是堵塞的,又會出現下面這樣的問題,若是咱們不但要掃描鼠標點擊,還要掃描鍵盤是否按下,因爲掃描鼠標時被堵塞了,那麼可能永遠不會去掃描鍵盤;
3. 若是一個循環須要掃描的設備很是多,這又會引來響應時間的問題;
因此,該方式是很是很差的


方式二:就是事件驅動模型
目前大部分的UI編程都是事件驅動模型,如不少UI平臺都會提供onClick()事件,這個事件就表明鼠標按下事件。事件驅動模型大致思路以下:
1. 有一個事件(消息)隊列;
2. 鼠標按下時,往這個隊列中增長一個點擊事件(消息);
3. 有個循環,不斷從隊列取出事件,根據不一樣的事件,調用不一樣的函數,如onClick()、onKeyDown()等;
4. 事件(消息)通常都各自保存各自的處理函數指針,這樣,每一個消息都有獨立的處理函數;

  

事件驅動編程是一種編程範式,這裏程序的執行流由外部事件來決定。它的特色是包含一個事件循環,當外部事件發生時使用回調機制來觸發相應的處理。另外兩種常見的編程範式是(單線程)同步以及多線程編程。

讓咱們用例子來比較和對比一下單線程、多線程以及事件驅動編程模型。下圖展現了隨着時間的推移,這三種模式下程序所作的工做。這個程序有3個任務須要完成,每一個任務都在等待I/O操做時阻塞自身。阻塞在I/O操做上所花費的時間已經用灰色框標示出來了

在單線程同步模型中,任務按照順序執行。若是某個任務由於I/O而阻塞,其餘全部的任務都必須等待,直到它完成以後它們才能依次執行。這種明確的執行順序和串行化處理的行爲是很容易推斷得出的。若是任務之間並無互相依賴的關係,但仍然須要互相等待的話這就使得程序沒必要要的下降了運行速度。

在多線程版本中,這3個任務分別在獨立的線程中執行。這些線程由操做系統來管理,在多處理器系統上能夠並行處理,或者在單處理器系統上交錯執行。這使得當某個線程阻塞在某個資源的同時其餘線程得以繼續執行。與完成相似功能的同步程序相比,這種方式更有效率,但程序員必須寫代碼來保護共享資源,防止其被多個線程同時訪問。多線程程序更加難以推斷,由於這類程序不得不經過線程同步機制如鎖、可重入函數、線程局部存儲或者其餘機制來處理線程安全問題,若是實現不當就會致使出現微妙且使人痛不欲生的bug。

在事件驅動版本的程序中,3個任務交錯執行,但仍然在一個單獨的線程控制中。當處理I/O或者其餘昂貴的操做時,註冊一個回調到事件循環中,而後當I/O操做完成時繼續執行。回調描述了該如何處理某個事件。事件循環輪詢全部的事件,當事件到來時將它們分配給等待處理事件的回調函數。這種方式讓程序儘量的得以執行而不須要用到額外的線程。事件驅動型程序比多線程程序更容易推斷出行爲,由於程序員不須要關心線程安全問題。

當咱們面對以下的環境時,事件驅動模型一般是一個好的選擇:

  1. 程序中有許多任務,並且…
  2. 任務之間高度獨立(所以它們不須要互相通訊,或者等待彼此)並且…
  3. 在等待事件到來時,某些任務會阻塞。

當應用程序須要在任務間共享可變的數據時,這也是一個不錯的選擇,由於這裏不須要採用同步處理。

網絡應用程序一般都有上述這些特色,這使得它們可以很好的契合事件驅動編程模型。

總結:異步IO涉及到了事件驅動模型,進程中維護一個消息隊列,當客戶端又請求時,就會把請求添加到消息隊列中,線程從消息隊列中輪詢取要處理的請求,遇到I/O阻塞時(操做系統處理調用I/O接口處理,與程序無關),則進行上下文切換,處理其餘請求,當I/O操做完成時,調用回調函數,告訴線程處理完成,而後再切換回來,處理完成後返回給客戶端  Nginx能處理高併發就是用的這個原理

 

 

 

Select\Poll\Epoll異步IO

Select\Poll\Epoll詳解: http://www.cnblogs.com/lianzhilei/p/5843277.html

IO多路複用、異步IO詳解: http://www.cnblogs.com/lianzhilei/p/5955526.html

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

進程與線程詳細對比-》 點擊

相關文章
相關標籤/搜索