一、多线程介绍
1、什么是线程?
- 线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。
- 线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
- 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
2、为什么要使用多线程?
- 线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄和其他进程应有的状态。
- 因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率。
- 线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境 包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
- 操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能高得要多。
3、总结起来,使用多线程编程具有如下几个优点:
- 进程之间不能共享内存,但线程之间共享内存非常容易。
- 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高。
- python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。
import threading
import time,os
'''
1、普通创建方式
'''
# def run(n):
# print('task',n)
# time.sleep(1)
# print('2s')
# time.sleep(1)
# print('1s')
# time.sleep(1)
# print('0s')
# time.sleep(1)
#
# if __name__ == '__main__':
# target是要执行的函数名(不是函数),args是函数对应的参数,以元组的形式存在
# t1 = threading.Thread(target=run,args=('t1',))
# t2 = threading.Thread(target=run,args=('t2',))
# t1.start()
# t2.start()
'''
2、自定义线程:继承threading.Thread来定义线程类,
其本质是重构Thread类中的run方法
'''
# class MyThread(threading.Thread):
# def __init__(self,n):
# super(MyThread,self).__init__() #重构run函数必须写
# self.n = n
#
# def run(self):
# print('task',self.n)
# time.sleep(1)
# print('2s')
# time.sleep(1)
# print('1s')
# time.sleep(1)
# print('0s')
# time.sleep(1)
#
# if __name__ == '__main__':
# t1 = MyThread('t1')
# t2 = MyThread('t2')
# t1.start()
# t2.start()
'''
3、守护线程
下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,
因此当主线程结束后,子线程也会随之结束,所以当主线程结束后,整个程序就退出了。
所谓’线程守护’,就是主线程不管守护线程的执行情况,只要是其他非守护子线程结束且主线程执行完毕,
主线程都会关闭。也就是说:主线程不等待守护线程的执行完再去关闭。
主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。
因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,
而进程必须保证非守护线程都运行完毕后才能结束。
'''
# def run(n):
# print('task',n)
# time.sleep(1)
# print('3s')
# time.sleep(1)
# print('2s')
# time.sleep(1)
# print('1s')
#
# if __name__ == '__main__':
# t=threading.Thread(target=run,args=('t1',))
# t.setDaemon(True)
# t.start()
# print('end')
'''
通过执行结果可以看出,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行
'''
'''
4、主线程等待子线程结束
为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待守护线程执行完毕再结束。
'''
# def run(n):
# print('task',n)
# time.sleep(2)
# print('5s')
# time.sleep(2)
# print('3s')
# time.sleep(2)
# print('1s')
# if __name__ == '__main__':
# t=threading.Thread(target=run,args=('t1',))
# t.setDaemon(True) #把子线程设置为守护线程,必须在start()之前设置
# t.start()
# t.join() #设置主线程等待子线程结束
# print('end')
'''
5、多线程共享全局变量
线程是进程的执行单元,进程是系统分配资源的最小执行单位,所以在同一个进程中的多线程是共享资源的。
'''
# g_num = 100
# def work1():
# global g_num
# for i in range(3):
# g_num+=1
# print('in work1 g_num is : %d' % g_num)
#
# def work2():
# global g_num
# print('in work2 g_num is : %d' % g_num)
#
# if __name__ == '__main__':
# t1 = threading.Thread(target=work1)
# t1.start()
# time.sleep(1)
# t2=threading.Thread(target=work2)
# t2.start()
'''
6、互斥锁(Lock)
由于线程之间是进行随机调度,当多个线程同时修改同一条数据时可能会出现脏数据,
所以出现了线程锁,即同一时刻只允许一个线程执行某些操作。
线程锁用于锁定资源,可以定义多个锁,像下面的代码,当需要独占某一个资源时,
任何一个锁都可以锁定这个资源,就好比你用不同的锁都可以把这个相同的门锁住一样。
由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,
如果没有很好地保护该对象,会造成程序结果的不可预期,也称为“线程不安全”。
为了防止上面情况的发生,就出现了互斥锁(Lock)
'''
# def work():
# global n
# lock.acquire()
# temp = n
# time.sleep(0.1)
# n = temp-1
# lock.release()
#
#
# if __name__ == '__main__':
# lock = threading.Lock()
# n = 100
# l = []
# for i in range(100):
# p = Thread(target=work)
# l.append(p)
# p.start()
# for p in l:
# p.join()
'''
7、递归锁:RLcok类的用法和Lock类一模一样,但它支持嵌套。
RLock类代表可重入锁(Reentrant Lock)。
对于可重入锁,在同一个线程中可以对它进行多次锁定,
也可以多次释放。如果使用 RLock,那么 acquire() 和 release() 方法必须成对出现。
如果调用了 n 次 acquire() 加锁,则必须调用 n 次 release() 才能释放锁。
由此可见,RLock 锁具有可重入性。也就是说,同一个线程可以对已被加锁的 RLock 锁再次加锁,
RLock 对象会维持一个计数器来追踪 acquire() 方法的嵌套调用,
线程在每次调用 acquire() 加锁后,都必须显式调用 release() 方法来释放锁。
所以,一段被锁保护的方法可以调用另一个被相同锁保护的方法。
'''
# def func(lock):
# global gl_num
# lock.acquire()
# gl_num += 1
# time.sleep(1)
# print(gl_num)
# lock.release()
#
#
# if __name__ == '__main__':
# gl_num = 0
# lock = threading.RLock()
# for i in range(10):
# t = threading.Thread(target=func,args=(lock,))
# t.start()
'''
8、信号量(BoundedSemaphore类)
互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,
比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
'''
# def run(n,semaphore):
# semaphore.acquire() #加锁
# time.sleep(3)
# print('run the thread:%s\n' % n)
# semaphore.release() #释放
#
#
# if __name__== '__main__':
# num=0
# semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
# for i in range(22):
# t = threading.Thread(target=run,args=('t-%s' % i,semaphore))
# t.start()
# while threading.active_count() !=1:
# pass
# else:
# print('----------all threads done-----------')
'''
9、python线程事件
用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下的几个方法:
clear将flag设置为 False
set将flag设置为 True
is_set判断是否设置了flag
wait会一直监听flag,如果没有检测到flag就一直处于阻塞状态
事件处理的机制:全局定义了一个Flag,
当Flag的值为False,那么event.wait()就会阻塞,
当flag值为True,那么event.wait()便不再阻塞
'''
event = threading.Event()
def lighter():
count = 0
event.set() #初始者为绿灯
while True:
if 5
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?