Python之路(第四十一篇)线程概念、线程背景、线程特点、threading模块、开启线程的方式

 

 

一、线程

​ 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

有了进程为什么要有线程

​ 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在三点上:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

  • 进程间的数据无法直接共享

线程的出现原因

正常情况下,我们在启动一个程序的时候。这个程序会先启动一个进程,启动之后这个进程会拉起来一个线程。这个线程再去处理事务。也就是说真正干活的是线程,进程这玩意只负责向系统要内存,要资源但是进程自己是不干活的。默认情况下只有一个进程只会拉起来一个线程。

​ 多线程顾名思义,就是同样在一个进程的情况同时拉起来多个线程。上面说了,真正干活的是线程。进程与线程的关系就像是工厂和工人的关系。那么现在工厂还是一个,但是干活的工人多了。那么效率自然就提高了。因为只有一个进程,所以多线程在提高效率的同时,并没有向系统伸手要更多的内存资源。因此使用起来性价比还是很高的。但是多线程虽然不更多的消耗内存,但是每个线程却需要CPU的的参与。

​ 相当于工厂虽然厂房就一间,可以有很多的工人干活。但是这些工人怎么干活还得靠厂长来指挥。工人太多了,厂长忙不过来安排一样效率不高。所以工人(线程)的数量最好还是在厂长(cpu)的能力(内核数)范围之内比较好。

 

线程出现的背景

​ 60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。

  因此在80年代,出现了能独立运行的基本单位——线程(Threads)

  注意:进程是资源分配的最小单位,线程是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出现冲突。

 

 

二、threading模块

 

multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性

 

开启线程的两种方式

 

1、方式一

  
  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)
  ​

  

 

2、方式二

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()

  

  

 

多线程与多进程区别

1、运行方式不同:

进程不能单独执行,它只是资源的集合。

进程要操作CPU,必须要先创建一个线程。

所有在同一个进程里的线程,是同享同一块进程所占的内存空间。

 

2、关系

进程中第一个线程是主线程,主线程可以创建其他线程;其他线程也可以创建线程;线程之间是平等的。

进程有父进程和子进程,独立的内存空间,唯一的标识符:pid。

 

3、速度

启动线程比启动进程快。

运行线程和运行进程速度上是一样的,没有可比性。

线程共享内存空间,进程的内存是独立的。

 

4、创建

父进程生成子进程,相当于复制一份内存空间,进程之间不能直接访问

创建新线程很简单,创建新进程需要对父进程进行一次复制。

一个线程可以控制和操作同级线程里的其他线程,但是进程只能操作子进程。

 

5、交互

同一个进程里的线程之间可以直接访问。

两个进程想通信必须通过一个中间代理来实现。

 

 

测试下进程和线程谁开启的速度快

  
  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

 

posted on 2019-05-19 23:30  Nicholas--  阅读(438)  评论(0编辑  收藏  举报

导航