一、简介:线程与进程:进程和线程都是虚拟概念,是用于描述程序的执行过程。如果把进程比喻为一个生产车间,那进程就是车间的流水线,每个进程必定自带一个线程,并且可以造多个线程。进程是资源单位,用于在内存中开辟一块独立空间。线程是执行单位,是真正被cpu所运行的代码的执行过程,而执行过程中需要的资源则都要向进程索取。
二、作用:为了节省内存资源和代码量。
三、小结:开设进程需要申请空间,并且拷贝代码,而同个进程的多个线程的数据是共享的,所以线程的开销要远远小于进程。比如文本编辑器的,获取输入、实时把内容投印到屏幕、自动保存等功能,就是用线程实现的。
四、创建线程的两种方式
# 常用方式 import threading def task(name): print('子线程开始') print(name) print('子线程结束') if __name__ == '__main__': t = threading.Thread(target=task, args=('tom',)) t.start() t.join() print('主线程结束') ''' 结果为: 子线程开始 tom 子线程结束 主线程结束 ''' # 类继承法 import threading class MyThread(threading.Thread): def __init__(self, name): super().__init__() self.name = name def run(self): print('子线程开始') print(self.name) print('子线程结束') if __name__ == '__main__': t = MyThread('tom') t.start() t.join() print('主线程结束') ''' 结果为: 子线程开始 tom 子线程结束 主线程结束 '''
五、用线程实现tcp服务端的并发效果
# tcp服务端 import socket import threading server = socket.socket() server.bind(('127.0.0.1', 3333)) server.listen(5) def talk(in_conn): while 1: try: recv_data = in_conn.recv(1024) if len(recv_data) == 0: break send_data = '收到:'.encode('utf-8') + recv_data in_conn.send(send_data) except Exception as e: print(e) break in_conn.close() if __name__ == '__main__': while 1: print('等待客户端连接...') conn, client_addr = server.accept() t = threading.Thread(target=talk, args=(conn,)) t.start()
# tcp客户端 import socket client = socket.socket() client.connect(('127.0.0.1', 3333)) while 1: send_data = input('发送:').strip().encode('utf-8') if len(send_data) == 0: continue client.send(send_data) recv_date = client.recv(1024) print(recv_date.decode('utf-8'))
六、线程对象的join方法:类似于进程对象。
七、共享性:同一个进程下的多个线程之间数据是共享的。
import threading tag = 999 def task(new_tag): global tag tag = new_tag print(tag) if __name__ == '__main__': t1 = threading.Thread(target=task, args=('111',)) t2 = threading.Thread(target=task, args=('222',)) print(tag) # 结果为999 t1.start() # 开启子线程1,把所在进程的tag改为了111,结果为 111 print(tag) # 结果为 111 t2.start() # 开启子线程2,把所在进程的tag改为了222,结果为 222 print(tag) # 结果为 222
八、线程名及线程数
import threading import time def task(): time.sleep(0.1) print(threading.current_thread().name) # 打印子线程自身线程名 if __name__ == '__main__': t1 = threading.Thread(target=task) t2 = threading.Thread(target=task) t1.start() t2.start() print(threading.current_thread().name) # 打印主线程名 print(threading.active_count()) # 打印所在进程的所有线程数量 ''' 结果为: MainThread 3 Thread-1 Thread-2 '''
九、守护线程与非守护线程:
1、守护线程:伴随进程结束而结束。
2、非守护线程:主线程结束,进程未必会立即结束,会等待所有非守护线程结束才结束。
import threading import time def task(sleep_time): print('子线程{}开始了'.format(threading.current_thread().name)) time.sleep(sleep_time) print('子线程{}结束了'.format(threading.current_thread().name)) if __name__ == '__main__': t1 = threading.Thread(target=task, args=(2,)) t2 = threading.Thread(target=task, args=(1,)) t1.daemon = True # 声明t1为守护线程 t1.start() t2.start() print('主线程结束了') ''' 结果为: 子线程Thread-1开始了 子线程Thread-2开始了 主线程结束了 子线程Thread-2结束了 '''
十、全局解释器锁GIL和自定义线程互斥锁
1、GIL不是所有python解释器的默认机制,而是Cpython解释器特有的默认机制。
2、GIL保证的是解释器级别,也就是进程级别的数据安全,即一个进程只配一个解释器,进程内所有线程则由这一个解释器切换执行,切换的依据就是看GIL的在哪个线程中。
3、因为GIL机制,一个进程中的多个线程无法同时执行,又因为一个进程只配一个解释器,而一个解释器只配一个核,所以多个线程无法利用多核实现同时执行,所以这是解释型语言的共性。
4、GIL锁无法自定义,默认会在线程阻塞时脱离线程而被其他线程获取,所以针对不同需求,有时还是需要用到自定义的互斥锁。
十二、单核和多核情况下多线程与多进程的取舍
1、单核:多进程只会产生复数的消耗,而多线程则节省了这部分消耗,所以选择多线程,但是现在单核计算机已成过去式,所以可以单核情况只是理论上的探讨,几乎不会有实际场景。
2、多核:
①计算密集型:cpu无需频繁切换,那多配几个cpu则会提高效率,就是使用多进程。
②IO密集型:cpu需要频繁切换,而实际执行时间很短,与此相比,开启进程的消耗会更大,所以用多线程。
import multiprocessing import time def math(): res = 1 for x in range(1, 99999): res *= x if __name__ == '__main__': start_time = time.time() p_list = [] for n in range(10): p = multiprocessing.Process(target=math) p.start() p_list.append(p) for p in p_list: p.join() end_time = time.time() print(end_time - start_time) # 结果为 8秒多
import threading import time def math(): res = 1 for x in range(1, 99999): res *= x if __name__ == '__main__': start_time = time.time() t_list = [] for n in range(10): t = threading.Thread(target=math) t_list.append(t) t.start() for t in t_list: t.join() end_time = time.time() print(end_time-start_time) # 结果为 22秒多
import multiprocessing import time def sleep(): time.sleep(1) if __name__ == '__main__': start_time = time.time() p_list = [] for n in range(300): p = multiprocessing.Process(target=sleep) p_list.append(p) p.start() for p in p_list: p.join() end_time = time.time() print(end_time - start_time) # 结果为 8秒多
import threading import time def sleep(): time.sleep(1) if __name__ == '__main__': start_time = time.time() t_list = [] for n in range(300): t = threading.Thread(target=sleep) t_list.append(t) t.start() for t in t_list: t.join() end_time = time.time() print(end_time - start_time) # 结果为 1秒多
3、小结:多进程和多线程都有各自的应用场景,基于此,可以用多进程下再开多线程的方式来实现互补,如此,既可以通过多进程来利用多核提高效率又可以视情况开多线程而节约消耗。