线程与进程
什么是线程
线程是进程一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个线程的其他的线程所拥有的全部资源。每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
多线程
多线程类似于同时执行多个不同程序,多线程运行有如下优点
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
注:线程间的数据是共享的
什么是进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
注:进程间的数据是不共享的
那么问题来了,线程进程能不能并发的去处理问题?答案是肯定的,但是在python中确不支持多线程的并发。而这并不是python语言本身的缺陷,而是python解释器(CPython)的问题,所以说无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。
在python解释器(CPython)中由于引入了GIL的概念,才有了上述问题的存在。但是我们首先要明确,这并不是python语言本身的缺陷。
说了这么多,线程进程有什么联系和区别?
二者关系
- 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以互相并发执行。
- 相对进程而言,线程是更加接近与执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
二者区别
进程和线程的主要区别在于他们是不同的操作系统资源管理方式。进程拥有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是
一个进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉后就等于整个进程都死掉。但是进程切换时,耗费资源
较大,效率更差一些。
- 简而言之,一个程序至少有一个进程,一个进程至少有一个线程
- 线程的划分尺度小于进程,使得多线程程序的并发性高
- 进程在执行过程中拥有独立的内存单元,而对个线程共享内存,从而极大的提高了程序的运行效率
- 线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行的入口、执行顺序序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 从逻辑角度看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行,但是操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
那么二者有什么优缺点呢?
进程:
- 优点:同时利用多个CPU,能够同时进行多个操作
- 缺点:耗费资源(重新开辟内存空间)
线程:
- 优点:共享内存。IO操作时,创造并发操作
- 缺点:抢占资源
说了那么多开始学习线程进程了
线程模块
Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join(): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
- getName(): 返回线程名。
- setName(): 设置线程名。
使用线程有两种调用方式直接调用和继承式调用
直接调用
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
继承式调用
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
线程锁(互斥锁)(不好)
开始我们说了,线程间的数据是共享的,如果同时有多个线程去修改数据,就会造成数据的错误,所以这时就需要我们给单个线程加把锁去管理。
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print('--get num:',num ) time.sleep(1) num -=1 #对此公共变量进行-1操作 num = 100 #设定一个共享变量 thread_list = [] for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num )
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 print('--get num:',num ) time.sleep(1) lock.acquire() #修改数据前加锁 num -=1 #对此公共变量进行-1操作 lock.release() #修改后释放 num = 100 #设定一个共享变量 thread_list = [] lock = threading.Lock() #生成全局锁 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num )
Rlock递归锁 (优先选择)
import threading import time globals_num = 0 lock = threading.RLock() def Func(): lock.acquire() #获取锁 global globals_num globals_num += 1 time.sleep(2) print(globals_num) lock.release() #释放锁 for i in range(10): t = threading.Thread(target=Func) t.start()
前面我们说了,在python解释器中不是已经默认给我加了一把GIL锁吗?那为什么我们还要自己去加锁,这不是多次一举吗?当然是否定的。Python的GIL来保证同一时间只能有一个线程来执行了,而这里的lock是用户级的lock,跟那个GIL没关系 。
信号量Semaphore
互斥锁中只允许一个线程修改数据,而信号量同时允许一定数量的线程修改数据。
import threading import time class MyThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(2) semaphore.release() if __name__ == '__main__': semaphore = threading.BoundedSemaphore(5) thrs = [] for i in range(20): thrs.append(MyThread()) for t in thrs: t.start()
import threading from random import randint import time class Produce(threading.Thread): def run(self): global L while True: val = randint(0,100) print("生产者", self.name,"Apped " + str(val),L) if lock_conn.acquire(): L.append(val) lock_conn.notify() lock_conn.release() time.sleep(3) class Consuner(threading.Thread): def run(self): global L while True: lock_conn.acquire() if len(L) == 0: lock_conn.wait() print("消费者", self.name,"Daleta" + str(L[0]),L) del L[0] lock_conn.release() time.sleep(1) if __name__ =="__main__": L = [] lock_conn = threading.Condition() threads = [] for i in range(5): threads.append(Produce()) threads.append(Consuner()) for t in threads: t.start() for t in threads: t.join()
同步条件Events
可以通过events实现两个线程或多个线程间的交互。如下例子
import threading import time class Boss(threading.Thread): def run(self): print("今天加班到22:00") event.isSet() or event.set() time .sleep(5) print("下班") event.isSet() or event.set() class Worker(threading.Thread): def run(self): event.wait() print("哎") time.sleep(1) event.clear() event.wait() print("终于下班了") if __name__ == '__main__': event = threading.Event() threads = [] for i in range(5): threads.append(Worker()) threads.append(Boss()) for t in threads: t.start() for t in threads: t.join()
队列queue
python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
Queue模块中的常用方法:
q=queue.Queue()
- q.qsize() 返回队列的大小
- q.empty() 如果队列为空,返回True,反之False
- q.full() 如果队列满了,返回True,反之False
- q.full 与 maxsize 大小对应
- q.get([block[, timeout]])获取队列,timeout等待时间
- q.get_nowait() 相当Queue.get(False)
- q.put(item) 写入队列,timeout等待时间
- q.put_nowait(item) 相当Queue.put(item, False)
- q.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
- q.join() 实际上意味着等到队列为空,再执行别的操作
import queue import threading message = queue.Queue(10) #创建为10的队列 def producer(i): #定义生产者模型 print("put:", i) message.put(i) def consumer(i): #定义消费者模型 msg = message.get() print(msg) for i in range(10): t = threading.Thread(target=producer, args=(i,)) t.start() for i in range(10): t = threading.Thread(target=consumer, args=(i,)) t.start()
import queue import threading import time exitFlag = 0 class myThread (threading.Thread): def __init__(self, threadID, name, q): threading.Thread.__init__(self) self.threadID = threadID self.name = name self.q = q def run(self): print("Starting " + self.name) process_data(self.name, self.q) print("Exiting " + self.name) def process_data(threadName, q): while not exitFlag: queueLock.acquire() if not workQueue.empty(): data = q.get() queueLock.release() print("%s processing %s" % (threadName, data)) else: queueLock.release() time.sleep(1) threadList = ["Thread-1", "Thread-2", "Thread-3"] nameList = ["One", "Two", "Three", "Four", "Five"] queueLock = threading.Lock() workQueue = queue.Queue(10) threads = [] threadID = 1 # 创建新线程 for tName in threadList: thread = myThread(threadID, tName, workQueue) thread.start() threads.append(thread) threadID += 1 # 填充队列 queueLock.acquire() for word in nameList: workQueue.put(word) queueLock.release() # 等待队列清空 while not workQueue.empty(): pass # 通知线程是时候退出 exitFlag = 1 # 等待所有线程完成 for t in threads: t.join() print("Exiting Main Thread")
多进程
在python中可以调用multiprocessing模块来创建多进程,multiprocessing模块可以给指定的机器充分的去利用CPU资源。
下面创建一个简单的进程
from multiprocessing import Process import time import os def info(title): print(title) print("父进程", os.getppid()) print("子进程", os.getpid()) if __name__ == "__main__": info("