Python之路(第四十一篇)線程概念、線程背景、線程特色、threading模塊、開啓線程的方式

 

 

1、線程

​ 以前咱們已經瞭解了操做系統中進程的概念,程序並不能單獨運行,只有將程序裝載到內存中,系統爲它分配資源才能運行,而這種執行的程序就稱之爲進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。在多道編程中,咱們容許多個程序同時加載到內存中,在操做系統的調度下,能夠實現併發地執行。這是這樣的設計,大大提升了CPU的利用率。進程的出現讓每一個用戶感受到本身獨享CPU,所以,進程就是爲了在CPU上實現多道編程而提出的。html

有了進程爲何要有線程

​ 進程有不少優勢,它提供了多道編程,讓咱們感受咱們每一個人都擁有本身的CPU和其餘資源,能夠提升計算機的利用率。不少人就不理解了,既然進程這麼優秀,爲何還要線程呢?其實,仔細觀察就會發現進程仍是有不少缺陷的,主要體如今三點上:python

  • 進程只能在一個時間幹一件事,若是想同時幹兩件事或多件事,進程就無能爲力了。程序員

  • 進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行。編程

  • 進程間的數據沒法直接共享服務器

線程的出現緣由

正常狀況下,咱們在啓動一個程序的時候。這個程序會先啓動一個進程,啓動以後這個進程會拉起來一個線程。這個線程再去處理事務。也就是說真正幹活的是線程,進程這玩意只負責向系統要內存,要資源可是進程本身是不幹活的。默認狀況下只有一個進程只會拉起來一個線程。markdown

​ 多線程顧名思義,就是一樣在一個進程的狀況同時拉起來多個線程。上面說了,真正幹活的是線程。進程與線程的關係就像是工廠和工人的關係。那麼如今工廠仍是一個,可是幹活的工人多了。那麼效率天然就提升了。由於只有一個進程,因此多線程在提升效率的同時,並無向系統伸手要更多的內存資源。所以使用起來性價比仍是很高的。可是多線程雖然不更多的消耗內存,可是每一個線程卻須要CPU的的參與。數據結構

​ 至關於工廠雖然廠房就一間,能夠有不少的工人幹活。可是這些工人怎麼幹活還得靠廠長來指揮。工人太多了,廠長忙不過來安排同樣效率不高。因此工人(線程)的數量最好仍是在廠長(cpu)的能力(內核數)範圍以內比較好。多線程

 

線程出現的背景

​ 60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨着計算機技術的發展,進程出現了不少弊端,一是因爲進程是資源擁有者,建立、撤消與切換存在較大的時空開銷,所以須要引入輕型進程;二是因爲對稱多處理機(SMP)出現,能夠知足多個運行單位,而多個進程並行開銷過大。併發

  所以在80年代,出現了能獨立運行的基本單位——線程(Threads)app

  注意:進程是資源分配的最小單位,線程是CPU調度的最小單位.

     每個進程中至少有一個線程。 

 

線程特色

  1)輕型實體

  線程中的實體基本上不擁有系統資源,只是有一點必不可少的、能保證獨立運行的資源。

  線程的實體包括程序、數據和TCB。線程是動態概念,它的動態特性由線程控制塊TCB(Thread Control Block)描述,

  2)獨立調度和分派的基本單位。  在多線程OS中,線程是能獨立運行的基本單位,於是也是獨立調度和分派的基本單位。因爲線程很「輕」,故線程的切換很是迅速且開銷小(在同一進程中的)。 

  3)共享進程資源。  線程在同一進程中的各個線程,均可以共享該進程所擁有的資源,這首先表如今:全部線程都具備相同的進程id,這意味着,線程能夠訪問該進程的每個內存資源;此外,還能夠訪問進程所擁有的已打開文件、定時器、信號量機構等。因爲同一個進程內的線程共享內存和文件,因此線程之間互相通訊沒必要調用內核。

  4)可併發執行。  在一個進程中的多個線程之間,能夠併發執行,甚至容許在一個進程中全部線程都能併發執行;一樣,不一樣進程中的線程也能併發執行,充分利用和發揮了處理機與外圍設備並行工做的能力。

 

線程和進程的關係

 

  • 根據上圖能夠看出,進程包含線程。也就是說默認狀況下 一個進程確定會有一個線程的,這個線程叫主線程。

  • 多線程也就是在一個進程裏面開出多個線程。多進程裏面也能夠包含多線程。

  • 多進程之間是不能夠直接通信的。可是因爲多線程是被同一個進程包裹,故多線程中資源共享便可以直接通信。

  • 進程自己不可以執行

  • 進程和線程不能比較誰快誰慢,兩個沒有可比性,進程是資源的集合,線程是真正執行任務的,進程要執行任務也要經過線程

  • 啓動一個線程比啓動一個進程快,線程上下文切換比進程上下文切換要快得多

 

 

python線程模塊的選擇

 Python提供了幾個用於多線程編程的模塊,包括thread、threading和Queue等。thread和threading模塊容許程序員建立和管理線程。thread模塊提供了基本的線程和鎖的支持,threading提供了更高級別、功能更強的線程管理的功能。Queue模塊容許用戶建立一個能夠用於多個線程之間共享數據的隊列數據結構。  通常避免使用thread模塊,使用更高級別的threading模塊,對線程的支持更爲完善,並且使用thread模塊裏的屬性有可能會與threading出現衝突。

 

 

2、threading模塊

 

multiprocess模塊的徹底模仿了threading模塊的接口,兩者在使用層面,有很大的類似性

 

開啓線程的兩種方式

 

一、方式一

  
  from threading import Thread
  import time
  ​
  def func(n):
      time.sleep(2)
      print("線程是%s"%n)
      global g
      g = 0
      print(g)
  ​
  if __name__ == '__main__':
      g = 100
      t_l = []
      for i in range(10):
          t = Thread(target=func,args=(i,))
          t.start()
          t_l.append(t)
      
      for t in t_l:
          t.join()
      print("主線程G",g)
  ​

  

 

二、方式二

import threading
import time


def func(n):
    time.sleep(2)
    print("線程是%s" % n)
    print('子線程的ID號A', threading.current_thread().ident)
    global g
    g = 0
    print('子線程中的g', g)


class Mythread(threading.Thread):

    def __init__(self, arg, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.arg = arg

    def start(self):
        print('start-----')
        super().start()  # 調用父類的start()和run()方法

    def run(self):
        print("類中的子線程", self.arg)
        super().run()
        print('子線程的ID號B',threading.current_thread().ident)


if __name__ == '__main__':
    g = 100
    t1 = Mythread('hello', target=func, name="MyThread", args=('nick',))
    # 第一個參數是用在Mythread類中的,後面的3個參數用在建立的func子線程中,args必須是可迭代的
    # 這裏的func也能夠直接寫在Mythread中的run()裏,這時這裏的run()不用再繼承父類的run()
    t1.start()
    # t1.run()
    t1.join()
    print('主線程中的g', g)
    print('主線程的ID號---', threading.current_thread().ident)

  簡化版:

import time
from threading import Thread
class MyTread(Thread):
    def __init__(self,arg):
        super().__init__()
        self.arg = arg
    def run(self):
        time.sleep(1)
        print(self.arg)
        print('直接在這裏寫子進程的代碼')

t = MyTread('hello')
t.start()

  

  

 

多線程與多進程區別

一、運行方式不一樣:

進程不能單獨執行,它只是資源的集合。

進程要操做CPU,必需要先建立一個線程。

全部在同一個進程裏的線程,是同享同一塊進程所佔的內存空間。

 

二、關係

進程中第一個線程是主線程,主線程能夠建立其餘線程;其餘線程也能夠建立線程;線程之間是平等的。

進程有父進程和子進程,獨立的內存空間,惟一的標識符:pid。

 

三、速度

啓動線程比啓動進程快。

運行線程和運行進程速度上是同樣的,沒有可比性。

線程共享內存空間,進程的內存是獨立的。

 

四、建立

父進程生成子進程,至關於複製一分內存空間,進程之間不能直接訪問

建立新線程很簡單,建立新進程須要對父進程進行一次複製。

一個線程能夠控制和操做同級線程裏的其餘線程,可是進程只能操做子進程。

 

五、交互

同一個進程裏的線程之間能夠直接訪問。

兩個進程想通訊必須經過一箇中間代理來實現。

 

 

測試下進程和線程誰開啓的速度快

  
  from multiprocessing import Process
  from threading import Thread
  import time
  ​
  def func(n):
      n + 1
  ​
  ​
  if  __name__ == "__main__":
      start_t = time.time()
      t_li = []
      for i in range(100):
          t = Thread(target=func, args=(i,))
          t.start()
          t_li.append(t)
      for t in t_li: t.join()
      druing_time1 = time.time() - start_t
  ​
      start_p = time.time()
      p_li = []
      for i in range(100):
          p = Process(target=func, args=(i,))
          p.start()
          p_li.append(p)
      for p in p_li: p.join()
      druing_time2 = time.time() - start_t
      print(druing_time1,druing_time2)

  

分析:根據執行結果顯示:開啓線程的速度比進程的速度快上百倍以上。

 

 

看下多線程和多進程的PID

  
  from threading import Thread
  from multiprocessing import Process
  import os
  ​
  def work():
      print('函數內hello',os.getpid())
  ​
  if __name__ == '__main__':
      #part1:在主進程下開啓多個線程,每一個線程都跟主進程的pid同樣
      t1=Thread(target=work)
      t2=Thread(target=work)
      t1.start()
      t2.start()
      print('主線程/主進程pid',os.getpid())
  ​
      #part2:開多個進程,每一個進程都有不一樣的pid
      p1=Process(target=work)
      p2=Process(target=work)
      p1.start()
      p2.start()
      print('主線程/主進程pid',os.getpid())

  

 

測試下多線程內共享數據的狀況

  
  from  threading import Thread
  from multiprocessing import Process
  import os
  def work():
      global n
      n=0
  ​
  if __name__ == '__main__':
  ​
      # 多進程的狀況
      # n=100
      # p=Process(target=work)
      # p.start()
      # p.join()
      # print('主',n)
      #毫無疑問子進程p已經將本身的全局的n改爲了0,但改的僅僅是它本身的,查看父進程的n仍然爲100
      #
      #
  ​
      # 多線程的狀況
      n=1
      t=Thread(target=work)
      t.start()
      t.join()
      print('主',n) 
      #查看結果爲0,由於同一進程內的線程之間共享進程內的數據,
      # 這表示線程內數據是共享的

  

 

多線程下的socket客戶端和服務端

服務端

  
  import socket
  from threading import Thread
  ​
  ​
  server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  server.bind(("127.0.0.1", 8081))
  server.listen(5)
  buffer_size = 1024
  ​
  def chat(conn,addr):
  ​
      res = conn.recv(buffer_size)
      print("addr",addr,res.decode("utf-8"))
      msg = input("請輸入:").strip()
      conn.send(msg.encode("utf-8"))
  ​
  def func():
  ​
      while True:
          print("服務器開始運行了")
          conn,addr = server.accept()
          res = Thread(target=chat,args=(conn,addr)).start()
  ​
  func()
 

  

客戶端

  
  import socket
  ​
  buffer_size = 1024
  client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  client.connect(("127.0.0.1",8081))
  ​
  while True:
      msg = input("請輸入").strip()
      client.send(msg.encode("utf-8"))
      res = client.recv(buffer_size)
      print(res.decode("utf-8"))

  

 

分析:這裏可測試同時開啓多個客戶端與服務端聊一次天

 

 

 

 

 

參考連接

[1]https://blog.csdn.net/u013421629/article/details/79208227

[2]https://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

相關文章
相關標籤/搜索