Python并发编程基础 №⑤ 线程理论、Threading(线程)模块、全局解释锁、Treading中的其他方法

线程理论

能独立运行的基本单位——线程(Threads)。
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程。 

进程和线程的关系
 线程与进程的区别可以归纳为以下4点:
  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  3)调度和切换:线程上下文切换比进程上下文切换要快得多。
  4)在多线程操作系统中,进程不是一个可执行的实体。

线程的特点
 在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
  1)轻型实体
  线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
  线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。

  2)独立调度和分派的基本单位。
  在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
  
3)共享进程资源。
  线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;
此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
  
4)可并发执行。
  在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与
外围设备并行工作的能力。

# 使用线程的实际场景
  开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,
因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
内存中的线程

  多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。
  而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。
  不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,
一个线程可以访问另外一个线程的内存地址,大家都是共享的,一个线程干死了另外一个线程的内存,那纯属程序员脑子有问题。
  类似于进程,每个线程也有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。
  线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:
  1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程
  2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?
  因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。

线程和python

 

1、全局解释器锁GIL
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。
虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
  对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
  在多线程环境中,Python 虚拟机按以下方式执行:
  a、设置 GIL;
  b、切换到一个线程去运行;
  c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
  d、把线程设置为睡眠状态;
  e、解锁 GIL;
  d、再次重复以上所有步骤。
  在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,
所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

2、python线程模块的选择
  Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。
thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用
于多个线程之间共享数据的队列数据结构。
  避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与
threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主
线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
  thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,
守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,
在进程退出的时候,不用等待这个线程退出。

3、threading模块
  multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍
(1)线程的创建threading.Thread类
 1 # 方式一:
 2 import os, time
 3 from threading import Thread
 4 
 5 
 6 def func(n):
 7     time.sleep(1)
 8     print(os.getpid(), n * n)  # ,每个线程都跟主进程的pid一样
 9 
10 print('主进程id:', os.getpid())
11 
12 # for i in range(1, 5): # 在主进程下开启多个线程
13 #     thread = Thread(target=func, args=(i,))
14 #     thread.start()
15 
16 # 方式二:
17 class MyThread(Thread):
18     # 传参时,必须重写Thread类的初始化方法
19     def __init__(self, arg):
20         super().__init__()
21         self.arg = arg
22 
23     def run(self):
24         print('tom')
25         for i in range(1, 5):
26             func(i)
27 
28 # my_thread = MyThread('tom')
29 # my_thread.start()
View Code

(2)、多线程与多进程
# 开启效率的较量 与内存数据共享
 1 from multiprocessing import Pool
 2 
 3 def add(x):
 4     global total_num  # 线程中,可以内存数据共享
 5     print(total_num + x)
 6 
 7 if __name__ == '__main__':
 8     total_num = 100  # 内存数据共享
 9     # start = time.time()
10     # pool = Pool(7)
11     # pool.map(add, range(100))
12     # t1 = time.time() - start
13 
14     sec_time = time.time()
15     thread_lst = []
16     for i in range(100):
17         thread = Thread(target=add, args=(i, ))
18         thread.start()
19         thread_lst.append(thread)
20     [thread.join() for thread in thread_lst]
21     t2 = time.time() - sec_time
22 
23     # print(t1, t2)  # 0.22891592979431152 0.14960026741027832  todo 可以看到即使使用了进程池,也没线程快!
View Code

(3)、Thread类的其他方法
Thread.is_alive()  # 返回线程是否活动的。
Thread.isDaemon() # 是否是守护线程
Thread.setDaemon() # 设置守护线程
Thread.setName() # 设置线程名
Thread.getName() # 返回线程名。
属性
Thread.daemon # 守护线程
Thread.name
Thread.ident


(4)、threading模块的一些方法:
threading.activeCount()  # 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
threading.enumerate() # 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.current_thread() # 返回当前的线程变量。
threading.get_ident() # 返回线程id
threading.main_thread() # 返回当前的主线程变量。
 1 def work():
 2     pass
 3 
 4 t = Thread(target=work)
 5 t.start()
 6 t.join()
 7 
 8 print(threading.active_count())
 9 print(threading.get_ident())
10 print(threading.main_thread().getName())
11 print(threading.current_thread())  # <_MainThread(MainThread, started 10828)>
View Code

 

使用线程实现socket的tcp聊天

服务器端

 1 import socket
 2 from threading import Thread
 3 
 4 
 5 # 子线程
 6 def chat(conn):
 7     conn.send(b'hello')
 8     print(conn.recv(1024).decode('utf-8'))
 9     conn.close()
10 
11 
12 # 主线程
13 sk = socket.socket()
14 sk.bind(('127.0.0.1', 8888))
15 sk.listen()
16 
17 while 1:
18     conn, addr = sk.accept()
19     Thread(target=chat, args=(conn, )).start()
20 
21 sk.close()
View Code

客户端

 1 import socket
 2 
 3 sk = socket.socket()
 4 sk.connect(('127.0.0.1', 8888))
 5 
 6 print(sk.recv(1024).decode('utf-8'))
 7 msg = input('>>>').encode('utf-8')
 8 sk.send(msg)
 9 
10 sk.close()
View Code
posted @ 2019-12-31 22:12  四方游览  阅读(224)  评论(0)    收藏  举报