并发编程之线程
多进程
核心--多道技术
切换+保存状态
程序I/O操作较多时,可提高程序效率
概念:线程是应用程序中工作的最小单元,或者又称之为微进程。
进程是一个资源单位,包含运行程序所需的所有资源,线程才是CPU上的执行单位
每个进程一旦被创建,就默认开启了一条线程,其为主线程,有且只有一个。
执行代码时,如遇I/O系统会自动切换到其他应用程序--降低了当前应用程序的效率。若进程中不仅有主线还有其他线程,主线遇到I/O时--切到其他线程,保证了应用程序对CPU利用率(占用率)
对于CPU而言,只有线程可以被执行,所以CPU切换是在不同线程之间进行切换,开启多线程可提高效率---
多线程即在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间
创建进程的开销要远大于线程
进程之间是竞争关系,线程之间是协作关系
使用线程:提高效率
为何不用多进程--进程对操作系统的资源耗费非常高
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
线程可以分为:
- 内核线程:由操作系统内核创建和撤销。
- 用户线程:不需要内核支持而在用户程序中实现的线程。
使用:
- threading(推荐使用)
thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 "_thread"。反正你不要再用就是了! 我习惯上用 from threading import Thread
两种开启线程的方式
1.实例化Thread
2.继承Thread类,覆盖run方法
# 开启进程第一种方式 from threading import Thread def task(): print("threading running") t1=Thread(target=task) t1.start() print("over") # 第二种方式 class MyThread(Thread): def run(self): print("子线程 running...") MyThrestart() print("over2")
当程序中遇到I/O时-------开启多线程,当程序中为纯计算任务时-----无法提高效率
多线程与多进程区别:
1.多个线程共享创建它的进程的地址空间,资源共享;进程有自己的地址空间。 2.线程可以直接访问其进程的数据段;进程有它们自己的父进程数据段的副本。 3.线程可以直接与其进程的其他线程通信;进程必须使用进程间通信来与同级进程通信。 4.新线程很容易创建;新进程需要重复父进程。 5.线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。 6.对主线程的更改(取消、优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。
进程对操作系统的资源耗费非常高,线程非常低
进程数据相互隔离,同一进程中线程数据共享
IO密集的程序 ---多线程---IO反正都要被阻塞,切换 线程的方式不会比进程慢多少
计算密集的程序 ---多进程 ---利用多核可以达到并行!
# 开启进程第一种方式 from threading import Thread def task(): print("threading running") t1=Thread(target=task) t1.start() print("over") # 对比进程启动时间 from multiprocessing import Process def task(): print("running") if __name__ == '__main__': t2=Process(target=task) t2.start() print("over...")
守护线程(setDaemon)
在(同进程下)所有非守护线程结束后结束
守护进程使用--生产者消费者模型
one) if __name__ == '__main__': q = JoinableQueue() #生产者1 c1 = Process(target=make_hotdog,args=("万达热狗店",q)) c1.start() #生产者2 c2 = Process(target=make_hotdog, args=("老男孩热狗店", q)) c2.start() # 消费者 p2 = Process(target=eat_hotdog,args=("思聪",q)) p2.start() # 首先保证生产者全部产完成 c1.join() c2.join() # 保证队列中的数据全部被处理了 q.join() # 明确生产方已经不会再生成数据了
threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法: threading.currentThread(): 返回当前的线程变量。 threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法: run(): 用以表示线程活动的方法。 start():启动线程活动。 join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 setDaemon(True):守护主线程,跟随主线程退(必须要放在start()上方) isAlive(): 返回线程是否活动的。 getName(): 返回线程名。 setName(): 设置线程名。
# 获取所有线程对象,返回一个列表 print(enumerate()) # 获取当前正在运行的线程个数 print(active_count()) # 获取当前线程对象 print(current_thread())
加锁
什么时候用锁 当多个进程或多个线程需要同时修改同一份数据时,可能会造成数据的错乱,所以必须得加锁
互斥锁
多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程互斥。
这两个对象都有 acquire 方法和 release 方法。
对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。
import time from threading import Thread,Lock lock =Lock() a = 100 def task(): lock.acquire() global a temp = a - 1 time.sleep(0.01) a = temp lock.release() ts = [] for i in range(100): t = Thread(target=task) t.start() ts.append(t) for t in ts: t.join() print(a)
Rlock--可重用锁
其实也是一种互斥(锁),特点是可以设置一个数据可以被一个线程/进程共享,即限制线程的并发量
与普通锁区别:
普通锁一旦加锁,则这个数据同一时间只能被一个线程使用
信号量 可以让这个数据在同一时间只能被多个线程使用
使用场景:
限制一个数据被同时的访问的次数,保证程序正常运行
import time from threading import Semaphore,Thread,current_thread sem=Semaphore(3) # 指定有几个线程共享 def task(): sem.acquire() # 先加锁 print("%s run..."% current_thread()) time.sleep(3) sem.release() for i in range(10): t=Thread(target=task) t.start()
练习: 1、基于多线程实现并发的套接字通信 2、基于多线程+TCP协议实现上传下载功能 3、编写一个文本处理工具,总共三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
练习
1、基于多线程实现并发的套接字通信
2、基于多线程+TCP协议实现上传下载功能
3、编写一个文本处理工具,总共三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
import socket client = socket.socket() client.connect(("127.0.0.1", 9090)) while True: msg = input(">>>:").strip() if not msg: continue client.send(msg.encode("utf-8")) import socket from threading import Thread server = socket.socket() server.bind(("127.0.0.1", 9090)) server.listen(3) def task(client): while True: try: data = client.recv(1024) print(data.decode("utf-8")) client.send(data.upper()) except Exception as e: print(e) if __name__ == '__main__': while True: client, addr = server.accept() t1 = Thread(target=task, args=(client,)) t1.start() print("hi")
客户端: import json import socket import struct from threading import Thread import common is_uploading = False is_downloading = False c = socket.socket() c.connect(("127.0.0.1", 9090)) def download(): global is_downloading if is_downloading: print("您有一个任务正在下载") return is_downloading = True print("ddddd") head = {"func": "download"} head_data = json.dumps(head).encode("utf-8") c.send(struct.pack("i", len(head_data))) c.send(head_data) common.recv_file(c) is_downloading = False # head={"func":"download"} # head_data=json.dumps(head).encode("utf-8") # c.send(struct.pack("i",len(head_data))) # c.send(head_data) def upload(): global is_uploading if is_uploading: print("您有一个任务正在上传.....") return is_uploading = True head = {"func": "upload"} head_data = json.dumps(head).encode("utf-8") c.send(struct.pack("i", len(head_data))) c.send(head_data) common.send_file(c, r"E:\2019.1\1.3 GIL\client111.py", "b.txt") is_uploading = False # head = {"func": "download"} # head_data = json.dumps(head).encode("utf-8") # c.send(struct.pack("i", len(head_data))) # c.send(head_data) func_dic = {"1": download, "2": upload} while True: res = input("请选择 1:上传 2:下载 Q:退出").strip() if res.upper() == "Q": break if res not in func_dic: print("error") continue Thread(target=func_dic[res]).start() 服务器端: from threading import Thread import socket,json,struct from concurrent.futures import ThreadPoolExecutor import common server=socket.socket() # server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) server.bind(("127.0.0.1",9090)) server.listen(5) pool=ThreadPoolExecutor() def recv_file(c): common.recv_file(c) print("recv ") def send_file(c): common.send_file(c) print("send ") def task(c): """ 接收用户数据,判断用户要执行的操作 :return: """ # 先接收报头 # c.recv(4),其为2进制需转 while True: try: len_data=c.recv(4) if not len_data: c.close() # 需加while ,否则发送完毕就断开链接 break head_len=struct.unpack("i",len_data)[0] head=json.loads(c.recv(head_len).decode("utf-8")) if head["func"]=="download": send_file(c) elif head["func"]=="upload": recv_file(c) else: print("require error") except ConnectionResetError: c.close() break
from threading import Thread import time msg_l=[] format_l=[] def receive(): while True: msg=input(">>>:").strip() if not msg: continue msg_l.append(msg) def format_msg(): while True: if msg_l: res=msg_l.pop(0) format_l.append(res.upper()) def save(): with open("db.txt","a",encoding="utf-8")as f: while True: if format_l: res = format_l.pop(0) f.write("%s\n")%res time.sleep(0.1) if __name__ == '__main__': t1=Thread(target=receive) t2=Thread(target=format_msg) t3=Thread(target=save) t1.start() t2.start() t3.start()