一、进程与线程
1.1进程
进程是每个独立运行着的程序称为一个进程
操作系统分配资源的最小单位,有独立的内存空间和系统资源
1.2线程(Thread)
线程是一个进程内部的一条执行路径(path)
进程中执行运算的最小单位,处理器分配给线程、即真正在处理器上运行的是线程
1.3进程和线程区别
进程有独立的地址空间,一个进程崩溃后,不会对其它进程产生影响,而线程只是一个进程中的一个执行路径,如有一条线程崩溃了,可能会影响同进程中的其他的线程。
线程有自己的栈和局部变量,多个线程共享同一进程的地址空间,一个进程至少有一个线程
1.4多线程的优缺点
多线程就是在一个进程中创建多个线程,每个线程完成一个任务
优点:多线程技术使程序的响应速度更快,提高资源利用率
缺点:程序设计复杂,上下文切换开销,增加资源消耗
注意:GIL(全局解释器锁)不支持多线程在多核CPU上运行。
1.5多线程的执行特性
随机性(异步执行),谁”抢”到cpu,谁执行 ,
宏观上同时执行,微观上同一时刻只能执行一个线程
多核除外
1.6 线程的生命周期(状态转换 )
线程的状态:要想实现多线程,必须在主线程中创建新的线程对象。任何线程都具有五种状态:创建、就绪、运行、阻塞、终止。
线程生命周期(状态转换) 1. 创建:实例化线程对象 2. 就绪状态:线程对象调用了start()方法,可以参与竞争CPU 3. 运行状态:获取CPU,执行线程代码 4. 阻塞状态:由于某种阻塞原因,线程发生阻塞,当阻塞原因解除,回到就绪状态 5. 终止状态:run()方法执行完毕,或其他终止情况。
注意:如果将线程设置为后台线程(通过设置线程对象的daemon=True变为后台线程), 则所有前台线程执行完毕后,进程就结束了,该后台线程自动结束(即是还没有执行完)。
二、线程
2.1 创建线程的方式
方式一: 编写一个继承自Thread(在threading模块中)的类,重写 run()方法,该run()方法中编写线程的执行代码; 使用线程对象的start()方法启动线程。 可以通过将参数传递到构造方法中,在run()方法中使用传递 的参数。
方式二: 实例化Thread对象,通过target参数指定线程将要执行的函数名, args参数通过元组来指定函数的位置实参,kwargs参数通过字典 来指定函数的关键字实参。 def func(address,age,sex): pass child_thread = threading.Thread(target=func,args=("南阳",25),kwargs={"sex":"男"}) child_thread.start()
代码演示
import threading # 线程类 import time class HelloThread(threading.Thread): def __init__(self,threadName,address): threading.Thread.__init__(self,name=threadName) # 调用父类的构造方法 self.address = address # 重写父类的run(),线程会调用该方法 def run(self): for i in range(10): print(threading.currentThread().getName()+"正在执行......===============") print("address="+self.address) if __name__ == '__main__': hello_thread = HelloThread("李元霸线程","长安") # 实例化线程对象 hello_thread.start() # 启动线程 for i in range(10): print(threading.currentThread().getName() + "正在执行......")
import threading def func(address,age,sex): for i in range(10): print(str(i)+" "+threading.currentThread().getName() + "正在执行......===============") print("func中的address="+address+" age="+str(age)+" sex="+sex) child_thread = threading.Thread(target=func,name="诸葛亮线程", args=("南阳",25),kwargs={"sex":"男"},daemon=True)#daemon设置成后台线程,如果前台线程结束,后台线程自动结束 child_thread.start() for i in range(10): print(threading.currentThread().getName() + "正在执行......")
2.2 threading.local()实现线程局部变量
import threading thread_local = threading.local() 针对thread_local对象的相同属性名,不同的线程有不同的局部变量。
代码演示
import random import threading import time class MyLocal: pass # mylocal = MyLocal() thread_local = threading.local() def func(): # mylocal.numbers = [] 该方法不能实现线程局部变量 thread_local.numbers = [] # 可以实现线程局部变量 # mylist = [] time.sleep(3) for i in range(10): thread_local.numbers.append(random.choice(range(10))) print(threading.currentThread().getName(),thread_local.numbers) for i in range(10): thread = threading.Thread(target=func) thread.start()
2.3 Lock实现锁机制
当多个线程共享数据时,有可能发生数据在逻辑上的错误(数据不能保持一致性), 这叫做线程不安全。可以在临界区(操作共享数据的某个区域)加锁的方式克服 线程不安全的问题。
import threadinglock = threading.Lock()lock.acquire() # 加锁lock.release() # 释放锁
代码演示
from threading import Thread,currentThread,Lock import time class TicketThread(Thread): ticket = 5 # 类属性,所有线程共享该数据 lock = Lock() # 类属性,实例化锁对象 def __init__(self,threadName): Thread.__init__(self,name=threadName) def run(self): for i in range(100): # 循环次数大于票数 TicketThread.lock.acquire() # 加锁 if TicketThread.ticket > 0: # 如果还有票 time.sleep(1) # 休眠3秒 TicketThread.ticket -= 1 print(currentThread().getName()+"卖票,当前剩余票数:"+str(TicketThread.ticket)) TicketThread.lock.release() # 释放锁 for i in range(1,4): # 创建三个线程 thread = TicketThread("窗口"+str(i)) thread.start() # 启动线程
2.4 条件变量Condition
经典案例:“生产者-消费者”问题 condition = threading.Condition() # 实例化线程条件变量对象 condition.acquire() # 获取该Condition对象上的锁(加锁) condition.release() # 释放该Condition对象上的锁(放锁) condition.wait() # wait()必须在加锁的情况下调用,wait()会释放已经获取的锁 condition.notifyAll() # notifyAll()必须在加锁的情况下调用,唤醒所有在该Condition对象上等待的线程
代码演示
import threading # 商品类 import time class Goods: def __init__(self): self.count = 0 def produce(self): self.count += 1 def consume(self): self.count -= 1 def isEmpty(self): return self.count <= 0 # 生产者线程类 class ProducerThread(threading.Thread): def __init__(self,threadName,condition,goods): threading.Thread.__init__(self,name=threadName) self.condition = condition self.goods = goods def run(self): time.sleep(2) while True: self.condition.acquire() # 使用条件变量,获取该Condition对象上的锁(加锁) self.goods.produce() # 生产商品 print(threading.currentThread().getName()+"生产了一个商品,当前商品数:"+str(self.goods.count)) self.condition.notifyAll() # notifyAll()必须在加锁的情况下调用,唤醒所有在该Condition对象上等待的线程 self.condition.release() # 使用条件变量,释放该Condition对象上的锁(放锁) # 消费者线程类 class ConsumerThread(threading.Thread): def __init__(self,threadName,condition,goods): threading.Thread.__init__(self, name=threadName) self.condition = condition self.goods = goods def run(self): time.sleep(2) while True: self.condition.acquire() # 使用条件变量,获取该Condition对象上的锁(加锁) if self.goods.count > 0: self.goods.consume() print(threading.currentThread().getName() + "消费了一个商品,当前商品数:" + str(self.goods.count)) else: print(threading.currentThread().getName() + "正在等待...") self.condition.wait() # wait()必须在加锁的情况下调用,wait()会释放已经获取的锁 self.condition.release() # 使用条件变量,释放该Condition对象上的锁(放锁) if __name__ == "__main__": goods = Goods() condition = threading.Condition() producer = ProducerThread("生产者线程",condition,goods) producer.start() # 启动生产者线程 for i in range(1,10): consumer = ConsumerThread(str(i)+"号消费者",condition,goods) consumer.start()
2.5 GIL(Global Interpreter Lock)
全局解释器锁: 加在Python解释器上的锁;对于多线程,GIL不支持 多个线程在多核CPU上运行。每一时刻,只有一个线程 在执行。
并发:宏观上,多线程同时执行;微观上,只有一个线程执行。并行:多个线程在微观上也是同时执行,每个线程在一个CPU核上执行。
2.6 线程队列
导入from queue import Queue, 实例化队列对象,通过在不同的线程中添加、获取(删除)队列中的数据, 实现线程间的数据传递。 q = Queue() # 实例化队列 q.put(i) # 当添加个数超过队列规定大小时,会发生阻塞 item = self.q.get() # 从队列中获取并删除数据,当队列中没有数据时会阻塞
代码演示
import time from queue import Queue from threading import Thread,currentThread class MyThread(Thread): def __init__(self,threadName,q): Thread.__init__(self,name=threadName) self.q = q def run(self): print(currentThread().getName()+"准备获取队列中的数据...") # time.sleep(2) for i in range(self.q.qsize()): item = self.q.get() # 从队列中获取并删除数据,当队列中没有数据时会阻塞 print(currentThread().getName() + "获取到的数据是:" + str(item)) if __name__ == '__main__': q = Queue() # 实例化队列 for i in range(6): q.put(i) # 当添加个数超过队列规定大小时,会发生阻塞 thread = MyThread("诸葛亮线程",q) thread.start()
2.7 线程的join()方法
线程对象调用join()方法后,当前线程必须等待,直到该线程对象对应的线程执行 完毕。
代码演示
import time,threading def func(): for i in range(10): time.sleep(1) print(threading.currentThread().getName()+"打印:"+str(i)) thread = threading.Thread(target=func) thread.start() thread.join() # 当前线程等待,调用join()方法的线程对象对应的线程执行完毕 for i in range(10): print(threading.currentThread().getName()+"打印:"+str(i))
三、homework
3.1 创建线程分别打印字母A和字母B
import threading import time def print_letter(letter): for i in range(5): print(letter) time.sleep(2) if __name__ == '__main__': left_thread = threading.Thread(target=print_letter,args=("A",)) left_thread.start() right_thread = threading.Thread(target=print_letter,args=("B",)) right_thread.start()
3.2 使用线程的Condition条件变量模拟两个线程互相等待现象
from threading import Condition,Thread,currentThread class MyThread(Thread): def __init__(self,threadName,condition,tag): Thread.__init__(self,name=threadName) self.condition = condition self.tag = tag def run(self): if self.tag == "A": self.condition.acquire() print(currentThread().getName() + "等待中...") self.condition.wait() #wait可以释放锁 print(currentThread().getName() + "结束等待.") else: self.condition.acquire() print(currentThread().getName() + "准备唤醒其他线程") self.condition.notifyAll() #唤醒所有 print(currentThread().getName() + "完成任务.") self.condition.release() condition = Condition() thread1 = MyThread("A线程",condition,"A") thread1.start() thread2 = MyThread("B线程",condition,"B") thread2.start()
3.3 创建线程打印偶数
import threading def print_even(start,end): for i in range(start,end+1): if i % 2 == 0: print(i) thread = threading.Thread(target=print_even,args=(1,100)) thread.start()