——
活动地址:CSDN21天学习挑战赛
——
多线程的理解就是两件或两件以上的事情通过代码同时发生。
而一般情况下我们写python代码的话是从上往下执行的,有一个先后顺序。
比如执行一下两行代码,就是先打印123 ,再打印456,而不是同时执行
print('123')
print('456')
而现在要学习实现的多线程就是要能够多个任务同时进行。
我们使用的 cpu 就可以执行多任务, 单核CPU 是操作系统轮流让各个任务交替执行, 任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务1执行0.01秒, 这样反复执行下去。 表面上看,每个任务都是交替执行的, 但由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
如今大多使用多核CPU,在 任务数量远远多于CPU的核心数量情况下, 操作系统会自动把很多任务轮流调度到每个核心上执行。
两个名词概念:
并发:
不是真正意义上的同时执行, 是任务数比cpu核数多,通过操作系统的各种任务调度算法,像前面讲的轮流执行,实现用多个任务“一起”执行,(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
并行:
任务数小于等于cpu核数,所有任务真的一起执行。
——
Python的 Thread 模块是比较底层的模块,python的 threading 模块是对 Thread 做了一些包装的,可以更加方便的被使用
threading模块常用方法
threading.active_count()
: 返回当前处于active状态的Thread对象
threading.current_thread()
: 返回当前Thread对象
threading.get_ident()
: 返回当前线程的线程标识符。线程标识符是一个非负整数,并无特殊含义,只是用来标识线程,该整数可能会被循环利用。
threading.enumerate()
: 返回当前处于active状态的所有Thread对象列表
threading.main_thread()
: 返回主线程对象,即启动Python解释器的线程对象。Python3.4及以后版本支持该方法
threading.stack_size()
: 返回创建线程时使用的栈的大小,如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(表示使用系统默认值)或大于32K的正整数
——
threading模块提供了Thread、Lock、RLock、Condition、Event、Timer
和Semaphore
等类来支持多线程,Thread是其中最重要也是最基本的一个类,可以通过该类创建线程并控制线程的运行。
threading.Thread 的方法和属性:
start()
: 启动线程。
run()
: 线程代码,用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为
init(self, group=None, target=None, name=None, args=(), kwargs=None, daemon=None)
:
构造函数
is_alive()
: 判断线程是否存活
getName()
: 返回线程名
setName()
: 设置线程名
isDaemon()
: 判断线程是否为守护线程
setDaemon()
: 设置线程是否为守护线程
name
: 用来读取或设置线程的名字
ident
: 线程标识,用非0数字或None(线程未被启动)
daemon
: 表示线程是否为守护线程,默认为False
join(timeout=None)
: 当 timeout 为 None 时,会等待至线程结束;当 timeout 不为 None 时,会等待至 timeout 时间结束,单位为秒。
使用thread 创建线程
语法格式:
threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=Nome)
参数:
group
: 通常默认即可,作为日后扩展 ThreadGroup 类实现而保留。
target
: 用于 run() 方法调用的可调用对象,默认为 None。
name
: 线程名称,默认是 Thread-N 格式构成的唯一名称,其中 N 是十进制数。
args
: 用于调用目标函数的参数元组,默认为 ()。
kwargs
: 用于调用目标函数的关键字参数字典,默认为 {}。
daemon
: 设置线程是否为守护模式,默认为 None。
threading
:Thread 的方法和属性
start()
: 启动线程。
——
正常单线程:
import threading
import time
def printt():
print("正在打印-")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
printt()
执行五次printt() 函数,过一秒打印一次,打印五次需要五秒。
——
def printt():
print("正在打印-")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=printt)
t.start()
调用start()
时,才会真正的创建线程,并且开始执行
五条打印会同时出现。
主线程会等待所有子线程结合后才结束
import threading
import time
def sing():
for i in range(3):
print("正在sing%d"%i)
time.sleep(2)
def dance():
for i in range(3):
print("正在dance%d"%i)
time.sleep(2)
if __name__ == "__main__":
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
print("------结束------")
两条两条打印,执行了第一个双线程,然后就打印"结束",再去执行线程中的循环打印。
实时线程数量查看
import threading
import time
def sing():
for i in range(3):
print("正在sing%d"%i)
time.sleep(1)
def dance():
for i in range(3):
print("正在dance%d"%i)
time.sleep(1)
if __name__ == "__main__":
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print("当前运行线程数:%d"%length)
if length<=1:
break
time.sleep(1)
运行过程:
——
使用threading
模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承 threading.Thread
就可以了,然后重写run方法
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = self.name + ' @ ' + str(i)
print(msg)
if __name__ == "__main__":
t=MyThread()
t.start()
threading.Thread 类有一个 run 方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过 Thread 类的 start 方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用 run 方法执行线程。
线程执行顺序查看
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = self.name + ' @ ' + str(i)
print(msg)
def test():
for i in range(5):
t=MyThread()
t.start()
if __name__ == "__main__":
test()
可以每五个同时输出,并且 多线程程序的执行顺序是不确定的。
代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
当线程的**run()**方法结束时该线程完成。
——
应该是在"数据结构"这门课程有。
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
优点:
确保了某段关键代码只能由一个线程从头到尾完整地执行
缺点:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
——
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
比如总共有资源1和资源2 ,线程1先占用了资源1 ,线程2先占用了资源2,但是现在线程1和线程2要进行下去都还缺少了资源。
此时线程1缺少资源2,线程2缺少资源1,但是缺少的资源都被对方占用了,双方都在等待对方释放自己需要的那部分资源,但是双方线程没有完成执行完都不会进行释放资源,导致进入了持续的等待状态,即死锁状态,程序无法继续运行下去。
避免死锁
优化程序设计,合理分配资源,比如有银行家算法,根据优先级分配资源。
添加超时时间,等待资源的过程如果超过时间,则自动结束,释放资源。