一、区别线程和进程
区分线程和进程:
进程在操作系统中分配得到资源
线程让操作系统运行代码的指针,是真真正正干活的人
|
|
可以把程序的执行认为是线程的指针的运动,指针指到哪行代码,哪行代码就被执行了
二、入门案例
在一个进程中开启多个线程,和开启进程的方法十分类似
|
#1.导入Thread |
三、Thread类
3.1Thread的构造方法:
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): |
|
group |
一般不用 |
|
target |
线程执行的目标函数 |
|
name |
给线程起别名 |
|
args |
以元组的方式给目标函数传递参数 |
|
kwargs |
以字典的方式给目标函数传递参数 |
|
daemon |
守护进程,一般不用 |
代码演示:
from threading import Thread import time def func(param1,param2): while True: print('【新的进程】param1=%s,param2=%s' % (param1,param2)) time.sleep(1) if __name__ == '__main__': thread = Thread(target=func,args=('value1','value2'),name='线程1') thread.start() while True: print(thread.name) time.sleep(1) |
3.2Thread的常用方法和属性
|
is_alive |
判断线程是否正在运行,返回True或者False |
|
join(timeout=None) |
堵塞主线程,指到子线程执行完或者超过最大的堵塞时间,才放行主线程,默认最大的堵塞时间无上限 |
|
getName() |
得到线程的别名,和直接获取name属性的结果一样 |
|
setName(name) |
设置线程的别名 |
代码演示:
from threading import Thread import time def func(): for i in range(5): print(i) time.sleep(1) if __name__ == '__main__': thread = Thread(target=func) thread.start() print('开启了thread线程') print('thread线程是否is_alive',thread.is_alive()) print('开始堵塞主线程') thread.join() thread.setName('子线程1') print('thread线程的name=',thread.getName()) print('thread线程是否is_alive',thread.is_alive()) |
3.3把线程封装成类对象、
步骤
1.定义子类继承Thread类
2.如果需要传递参数的话,重写__init__()方法
3.重写run方法,在thread.start()会直接运行类中的run方法
代码演示:
from threading import Thread import time #定义一个类,继承Thread class My_Thread(Thread): #重写__init__()方法 def __init__(self,param): #调用父类Thread的构造方法 Thread.__init__(self) self.param = param #重写run方法,被start()调用 def run(self): for i in range(5): print('子线程打印param=',self.param) time.sleep(1) if __name__ == '__main__': #实例化一个My_Thread类 thread = My_Thread('value') #使用start方法,执行类中的run方法 thread.start() |
3.4多线程的生命周期
通过Thread类创建出来的运行的线程,如果执行完毕,就会被销毁
如果子线程还在运行,但主线程的代码已经执行完毕,主线程会等待子线程执行完,再停止主线程
如果主线程被关闭,那么其余正在执行的子线程也会被关闭
四、线程中的通信
4.1使用全局变量通信
线程中的通信比进程通信方便的多,各个线程共享全局变量
from threading import Thread import time param_s = [] def func(): for i in range(5): print(param_s) param_s.append(i) time.sleep(1) if __name__ == '__main__': thread1 = Thread(target=func) thread2 = Thread(target=func) thread1.start() thread2.start()
|
4.2多线程操作全局变量的问题
from threading import Thread a = 0 #定义目标函数,修改全局变量a def func(): global a for i in range(100000): a += 1 if __name__ == '__main__': #实例化线程1和线程2 thread1 = Thread(target=func) thread2 = Thread(target=func) #开启线程1和线程2 thread1.start() thread2.start() #堵塞主线程,等待线程1和线程2执行完 thread1.join() thread2.join() #打印a的值 print(a) |
理论上,最终打印的a应该等于20000,但实际上a的值却小于20000
这是为什么呢????
在多线程对同一个全局变量的操作密度非常的情况下,就可能发生两个线程同时更改这个全局变量,导致这两个的更改中有一方的操作丢失。
|
|
4.3同步和异步
如何解决多线程一起操作共享资源的不安全性,先需要明白两个基本概念——同步和异步
线程同步,并不是指两个线程一起同时执行,而是彼此相互依赖,就像双腿走路一样,右脚等着左脚落稳了,再往前迈,线程的执行有先后顺序,一个线程等待另一个线程执行到某一个阶段,再往下执行,主线程只有一个
|
|
而异步:
多个任务的执行没有先后顺序,自己执行执行自己的
4.4线程锁
|
|
threading模块提供了一个Lock类,来实现线程锁
from threading import Lock
常用命令
|
lock = Lock() |
实例化一个线程锁 |
|
lock.acqure(block,timout) |
如果lock没有上锁,那么就把lock锁起来,往下执行,如果lock已经被上锁了,就会堵塞在这里,指到lock解锁 block是设置是否堵塞 timout是设置堵塞的最大时间 |
|
lock.release() |
解锁 |
|
lock.locked() |
返回lock的状态是否上锁,True或False |
代码演示:
from threading import Thread,Lock a = 0 def func(): global a #在对全局变量a取值操作之前,给线程上锁 lock.acquire() for i in range(100000): a += 1 #操作完全局变量,解锁 lock.release() if __name__ == '__main__': lock = Lock() thread1 = Thread(target=func) thread2 = Thread(target=func) thread1.start() thread2.start() thread1.join() thread2.join() print(a) |
以同步的方式,最终输出的a = 200000
4.5线程安全队列
python在queue模块中为我们提供了Queue()作为线程安全队列。
测试Queue()的安全性:
quque模块中的Queue使用方法几乎multiprocessing中的Queue和multiprocess.
Manager().Queue()完全一致
from threading import Thread from queue import Queue queue = Queue() #定义个函数,每次执行这个函数,都会向quque队列中加入一百万个数字 def func(): global queue for i in range(100000): queue.put(i) if __name__ == '__main__': #创建100个线程,把线程对象保存在thread_s集合中 thread_s = [] for i in range(100): thread_s.append(Thread(target=func)) #开启这100个线程 for thread in thread_s: thread.start() #等待所有的子线程执行完成 for thread in thread_s: thread.join() #打印queue队列的长度 print(queue.qsize()) |
最终的到队列的长度和所有线程向队列中加入值的数量总和相同,没有发现丢失的情况
在线程高并发的情况下,可以使用Queue()来处理线程通信
4.6模拟得到高并发时候,用户访问网站的点击量
from threading import Thread from queue import Queue #全局变量统计网站的点击次数 click_rate_num = 0 #用于统计点击量 def count_click_rate(): """ 如果可以从queue_click拿到一个值,就把click_rate_rum +1 队列的最大等待时间是5秒,5秒后仍没有拿到队列数据,就结束循环 """ global click_rate_num while True: try: queue_click.get(timeout=5) click_rate_num += 1 except: break def user_request(): for i in range(1000): #处理请求 #给用户响应过去网页资源 #点击量+1 queue_click.put(1) #释放资源 if __name__ == '__main__': #实例化一个用于统计点击量的队列 queue_click = Queue() #每个线程表示不停的在访问网站的用户 #创建100个这样的线程,保存在user_thread_s集合里面 user_thread_s = [] for i in range(100): user_thread_s.append(Thread(target=user_request)) #创建一个符合统计全局点击量的线程,并开启 count_click_thread = Thread(target=count_click_rate) count_click_thread.start() #开启模拟用户点击的线程 for user_thread in user_thread_s: user_thread.start() #等待所有的用户点击线程都结束了之后,放行主线程 for user_thread in user_thread_s: user_thread.join() #等待统计点击量的线程运行完,放行主线程 count_click_thread.join() #打印总共获得的点击量 print(click_rate_num) |
五、线程的私有变量
如何让每个线程有自己独立的数据呢:
- 函数中的参数,每个函数中自己的参数只在这个函数中有效。
- 把子线程要执行的函数封装成一个类对象,每个实例化的类对象的self.的变量是独立的
- 使用ThreadLocal
使用threading的local类
from threading import Thread,local import time #实例化一个local类对象,每个线程对于这个类的操作都是独立的,互不影响 thread_local = local() #初始化一个therad_local_param的值,然后循环打印 def func1(): thread_local.param = 'func1' while True: print('func1函数的thread_local_param=',thread_local.param) time.sleep(1) #初始化一个therad_local_param的值,然后循环打印 def func2(): thread_local.param = 'func2' while True: print('func2函数的thread_local_param=',thread_local.param) time.sleep(1) if __name__ == '__main__': thread1 = Thread(target=func1) thread2 = Thread(target=func2) thread1.start() thread2.start() |