一,线程的实现(线程由计算机本身配置实现)
-
什么是进程?进程就是正在运行的程序。进程主要是管理资源的,不是去实现功能的, 包工头:提供资源
-
什么是线程?线程时区实现功能的,比如说计算任务,变量的加法运算。线程由操作系统实现 建造者:修房子,建筑的事情就由工人做
-
一个进程一定至少有一个线程(一般俗称主线程)
-
为什么要写多线程?
- 比如我有一件事情,我被要求执行10次,每次时间为2秒
- 如果我要用for循环,那我至少要20秒
- 开辟多线程,能够快速完成这样的事情
-
Python通过两个标准库
_thread
和threading
提供对线程的支持,threading
对_thread
进行了封装。threading
模块中提供了Thread,Lock,Rlock,Condition等组件。 -
多线程的使用方法1
# 1,生成Thread对象 import time from threading import Thread def hello(name): print('hello,{}'.format(name)) time.sleep(3) print('bye') def hi(): print('hi') time.sleep(3) print('goodbye') if __name__ == '__main__': hello_thread = Thread(target=hello, args=('xyb',), name = '线程2', daemon=True) hi_thread = Thread(target=hi) # 1.1,创建两个线程对象 # 1.2,target参数接收函数对象本身 # 1.3,args参数接收值传参到函数里,且只能是元组 # 1.4,name参数用来设置线程名字(方法1) # 1.5, daemon参数用来设置守护线程(方法1) hi_thread.setName('线程3') # 2.1,setName用来设置线程的名字(方法2) print(hello_thread.getName()) # 2.2,getName用来获取线程的名字 print(hi_thread.getName()) hi_thread.setDaemon(True) # 3,setDaemon可以设置守护线程,它可以 (方法2) # 在主线程结束时也结束该子线程(无join时) # 必须放在start的前面 hello_thread.start() hi_thread.start() # 4,start开始执行线程里的程序 hello_thread.join() # 5,join方法会阻塞主线程,等待这两个子 hi_thread.join() # 线程执行完在执行主线程的print任务 print('主线程执行完毕')
-
多线程的使用方法2
import time from threading import Thread class MyThread(Thread): def __init__(self, people_name, *args, **kwargs): # 重写__init__方法 super().__init__(*args, **kwargs) # 调用父类的__init__方法 self.people_name = people_name # 设置改实例的属性 def run(self): print('hello, {}'.format(self.people_name)) # 进行传参 time.sleep(3) print('bye') my_thread = MyThread('邢益斌', name='线程2') my_thread.start() # 线程里面调用start就是调用run方法 print(my_thread.getName()) # 打印出改线程的名字
二,线程通信
- 在多线程中,所有变量对于所有线程都是共享的,因此,线程之间共享数据最大的危险在于多个线程同时修改一个变量,那就乱套了,所以我们需要互斥锁,来锁住数据。
- 线程锁方法1:
-
2.1,当**没有线程锁**的时候,两个线程访问同一个变量,会起冲突。
# 两个线程在访问同一个变量会起冲突!! rom threading import Thread x = 0 n = 100000 def hello(n): global x for i in range(n): x += 1 def hello1(n): global x for i in range(n): x -= 1 i = Thread(target=hello, args=(n,)) d = Thread(target=hello1, args=(n,)) i.start() d.start() i.join() d.join() print(x) # ---> 输出: # 最后x的值不是确定的一个数,两个线程抢变量,不能完整执行每个线程里的代码
-
2.2,当**有线程锁**时,运行速度会变慢,但是会把每个线程里的命令完整执行
from threading import Thread, Lock # 导入Lock锁模块 x = 0 n = 100000 lock = Lock() def hello(n): global x for i in range(n): lock.acquire() # 线程上锁,其他线程不能访问当前线程使用的变量 x += 1 lock.release() # 线程解锁,变量x可以被其他线程访问 def hello1(n): global x for i in range(n): with lock: # 第二种方式:使用上下文管理,自动上锁解锁 a -= 1 i = Thread(target=hello, args=(n,)) d = Thread(target=hello1, args=(n,)) i.start() d.start() i.join() d.join() print(x)
- 线程锁方法2(队列:一个入口,一个出口,先入先出)
-
3.1通过队列传递数据,安全,不会造成多个线程访问时混乱
from threading import Thread from queue import Queue from random import randint # 导入随机数模块 my_queue = Queue(10) # 参数的含义:当前这个队列最多可存放的元素 def hello(my_queue): for i in range(10): num = randint(0, 1000) my_queue.put(num) # 在当前my_queue队列中添加10个随机数 my_queue.join() # 只要队列里面不为空就会阻塞主程序,不让它运行 def hello1(my_queue): A for i in range(5): num = my_queue.get() # 在当前my_queue队列中取出5个数 my_queue.task_done() # 每次队列get值之后都跟上,更新底层记计数器 empty都是检测计数器来返回True和False print(num) # 打印这5个数 i = Thread(target=hello, args=(my_queue,)) d = Thread(target=hello1, args=(my_queue,)) i.start() d.start() i.join() d.join() print(my_queue.empty()) # 根据实际队列里面的任务返回True False 而不是计数器 # 输出---> 703 700 602 358 230
三,线程池
-
创建线程池类(通过队列实现)
from threading import Thread # 导入线程模块 from queue import Queue # 导入队列模块 import time class ThreadPool(object): def __init__(self, n): self.queue = Queue() # 设置初始化实例的queue属性为一个队列 for i in range(n): # 生成n个线程 Thread(target=self.worker, daemon=True).start() # target传入的是worker方法对象,开启守护线程,并start开始线程运行worker方法 # n有多少个创建的线程就有多少个 def worker(self): while True: # 从队列里面依次取出put方法放进去的值,运行传入的函数对象本身 func, args, kwargs = self.queue.get() # 取出一组元组数据,赋值。当队列里面的任务为空时,但是这是一个死循环,取不到值就卡主了,需要守护线程结束掉 func(*args, **kwargs) # 执行函数 self.queue.task_done() # 刷新底层计数器,以表示这个任务被取出 def put(self, func, args=(), kwargs={}): self.queue.put((func, args, kwargs)) # 在队列中加入一组元组数据 def join(self): self.queue.join() # join作用为根据底层计数器的值来执行是否阻塞队列 def hello(name, age): print('hello,{},{}岁'.format(name, age)) time.sleep(2) print('bye,{}'.format(age)) tp = ThreadPool(10) # 创建10个线程 for i in range(10): # 在队列里面加入10个func, args, kwargs格式的数据 tp.put(hello, args=(i,), kwargs={'age': i}) tp.join() # 根据计数器的值来执行是否阻塞队列,底层计数器为0就不阻塞,不为0就阻塞线程。队列里面的get方法有一个坑,就是如果队列里面的值为0,他还会是去取值,但是会直接阻塞线程!! 只能用守护线程来解决这个坑 """ 手动线程池: 主要配合队列来进行实现,我们定义好一个队列对象,然后将我们的任务对象put到我们的队列对象中,使用多线程,让我们的线程get队列中的对象,然后各自去执行自己get到的任务 这样的话其实也就实现了线程池 """
-
Python内置线程池
from multiprocessing.pool import ThreadPool # 这个内置线程池类似我们上面定义的线程池类 def hello(name, age): print('hello,{},{}岁'.format(name, age)) time.sleep(2) print('bye,{}'.format(age)) tp = ThreadPool(5) # 1 这个内置线程池其实是一个类,这里进行类的实例化,线程数为5 for i in range(10): tp.apply_async(hello, args=(i,), kwds={'age': i}) # 把任务提交到内置线程池 tp.terminate() # 终止线程池,终止所有任务 tp.close() # 2.关闭线程池,必须做,停止提交任务到线程池,但不会结束猪吃菜 tp.join() # 3.阻塞主线程,等待线程执行任务 """ 1. 创建线程池. 线程池:猪圈,线程池中的线程:猪 2. 将任务扔进去. 任务是白菜 3. 关闭线程池. 停止将任务发送给线程池,也就是停止投送白菜 4. 等待线程任务执行完毕. 等待圈中的猪把已经投进去的白菜吃完 """ cpu为4核就是4个系统线程,超线程技术:4核8线程 4核12线程等等...