多线程是多任务的一种方式,多线程的目的就是解决并发,提高处理效率.
多线程有两个概念:
1.并行:并行的前提是必须是多核CPU才可以发生.
并行的意思就是同时执行,成为并行
2.并发:并发可以发生在单核CPU和多核CPU中.
并发的意思就是在一定时间内执行任务,成为并发
一.创建多线程的方法有两种
(一).threading,Thread类的实例对象,即创建一个threading.Thread对象
import time
import threading
def say_sorry():
print("世纪东方垃圾收快递费了")
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
# Thread是一个类,创建一个线程执行的计划,target指定线程在执行的时候执行的函数
# threading.Thread返回的是一个Thread对象
thd = threading.Thread(target=say_sorry)
# 调用start()方法,创建一个线程,并且执行这个线程
thd.start()
# threading.enumerate()返回当前所有的线程
print(threading.enumerate())
# 打印当前执行的线程的数量
print(len(threading.enumerate()))
(二).第二种创建线程的方式为继承threading,Thread类,成为他的子类,并重写run()方[常用]
import threading
import time
class my_thread(threading.Thread):
"""继承threading.Thread类"""
# 重写run()方法
def run(self):
for i in range(500):
print("子线程执行了")
time.sleep(0.2)
if __name__ == '__main__':
# 创建自己的线程对象
mt = my_thread()
# 调用当前对象的Tread子类的start()方法的时候,默认执行的run()方法
mt.start()
while True:
# 打印执行的线程
print(threading.enumerate())
time.sleep(2)
线程的调度是根据cpu的调度方式决定的,window是抢占式调度,linux是一次调度(不确定,暂时这么记)
在线程调度时,默认情况下主线程会等待子线程全部执行完再释放 查看当前所执行的线程:threading.enumerate().返回一个元祖
二.给子线程传递参数
(一).创建threading.Thread对象创建线程的方式,给线程传参的代码如下
import threading
from time import sleep
def sing(num1, num2):
for i in range(3):
print("唱歌%d 参数1=%d 参数2=%d" % (i, num1, num2))
sleep(1)
def dance(str1, str2):
for i in range(3):
print("跳舞%d参数1=%s参数2=%s" % (i, str1, str2))
sleep(1)
if __name__ == '__main__':
# 创建唱歌的执行计划
# 给线程传递参数用args,args是一个元祖,
sing_thd = threading.Thread(target=sing, args=(100, 22))
dance_thd = threading.Thread(target=dance, args=("dd", "的方式"))
# 创建线程并且执行
sing_thd.start()
dance_thd.start()
(二).实现threading.Thread类,创建线程的方法,给线程传递参数的代码如下(自己试验)
注意:在创建自己的线程对象的时候,给自己线程对象,初始化参数之前,必须调用threadingt.Thread.__init__()方法,为线程初始化,否则会报错,显示自己的线程对象的__init__()没有被调用
import threading
import time
class my_thread(threading.Thread):
# 给线程传递参数,创建一个变量
def __init__(self, num):
# 在自己的线程类中重写init方法时,必须先调用threasing.Thread的init方法,
# 如果没有先初始化线程对象,将会报错Python RuntimeError: thread.__init__() not called异常
threading.Thread.__init__(self)
self.num = num
def run(self):
for e in range(3):
time.sleep(1)
print(self.name+"----------"+str(e)+"传入的参数%d" % self.num)
if __name__ == '__main__':
# for i in range(5):
mt = my_thread(11)
mt.start()
# while True:
# if len(threading.enumerate()) == 1:
# print("就剩我自己 了吧")
# break
mt.join(10)
print("子线程全部执行完了")
三.如有需求要在所有子线程执行后,在执行部分逻辑,有以下两种方法可以实现
一个是判断当前执行的线程的元祖的数量的长度是否等于1,如果等于1,说明只剩下了主线程,代码如下:
# for i in range(5):
mt = my_thread(11)
mt.start()
# while True:
# if len(threading.enumerate()) == 1:
# print("就剩我自己 了吧")
# break
二是调用threading.Thread或者其子类的join()方法:
if __name__ == '__main__':
# for i in range(5):
mt = my_thread(11)
mt.start()
# join()方法是判断是否所有子线程已经执行完,若没有执行完,将堵塞本线程,不向下执行
# 如所有子线程已经执行完,将执行join()后面的代码
# join(10)参数10,为判断是否还有子线程的超时时间
mt.join(10)
print("子线程全部执行完了")
四.同一个进程中多个线程共享数据的问题
若同一个进程中多个线程共享一个数据,将会出现数据的安全问题,即非线程安全
解决非线程安全的一个方法就是,在对共享数据进行更改时,先去判断这资源是否被其他线程使用,
互斥锁:即对资源的锁定,有两种状态,分别为锁定/非锁定
1.创建锁对象:lock = threading.Lock()
2. 将锁对象传入每个线程中
3.在对共享数据修改之前调用所对象的lock.acquire()---[acquire:"获得"]方法
# acquire()的含义就是去尝试的获取锁,如果没有被锁定,将会被我锁定. # 如果已经被锁定,那么将阻塞等待,直到另一方释放锁,然后自己得到锁.继续执行
4.处理完共享数据之后,需要释放锁,以便于其他线程获取数据,调用lock.release()-----[release:释放]
这种处理锁的方法,在多线程执行时,效率低,容易造成死锁.
对共享数据的处理如下:
import threading
import time
g_number = 0
# TODO 两个线程同时对一个全局变量进行修改,会发生非线程安全的问题
# TODO 解决方案:同步线程,为共享资源添加互斥锁,互斥锁有两种状态:锁定/非锁定
# TODO 共享资源只允许一个资源访问
def worker1(lock1):
global g_number
for i in range(1000000):
# 在修改全局变量之前,调用lock()方法,
# acquire()的含义就是去尝试的获取锁,如果没有被锁定,将会被我锁定.
# 如果已经被锁定,那么将阻塞等待,直到另一方释放锁,然后自己得到锁.继续执行
lock1.acquire()
g_number += 1
# 释放锁
lock1.release()
def worker2(lock1):
global g_number
for i in range(1000000):
lock1.acquire()
g_number += 1
lock1.release()
if __name__ == '__main__':
# 创建一把锁
lock = threading.Lock()
worker1_thread = threading.Thread(target=worker1, args=(lock,))
worker2_thread = threading.Thread(target=worker2, args=(lock,))
worker1_thread.start()
worker2_thread.start()
# 等两个线程执行完 再执行下面的代码
worker1_thread.join()
worker2_thread.join()
print(g_number)
未完待续...