Python学习七(线程)
1.线程
一个进程中至少包含一个线程,如果只有一个线程,那该线程为程序本身。线程有时也被称为轻量进程(lightweight process,LWP),是程序执行的最小单元。
线程往往比进程运用更广泛。
线程的特点如下:
- 线程是进程中的一个实体,是被系统独立调度和分派的基本单元,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与属于统一进程的其他线程共享进程所拥有的全部资源。
- 一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。
- 线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。
- 在单个程序中同时运行多个线程完成不同的工作,称为多线程。
由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程有就绪、阻塞和运行三种基本状态
- 就绪状态是指线程具有运行的所有条件,逻辑上可以运行,在等待处理机调度。
- 运行状态是指线程占用处理机正在执行。
- 阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
见例程:
def thread_test(param):print("[TEST]:%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < param:cnt += 1print("[TEST]:%s cnt:%s" %(threading.current_thread().name,cnt))time.sleep(1)if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))t = threading.Thread(target=thread_test, name='test_thread' ,args=(5,)) #创建一个线程,线程名字为test_thread。print("thread start")t.start()print("wait thread end")t.join()print("thread end")
结果:
可以看到,线程在被创建好之后是一个就绪状态,并没有开始执行,在调用了start()之后,线程才开始执行。而调用join()后,主任务阻塞等待线程运行完成。 此外,在主进程和线程中都打印了进程ID,发现ID是相同的,都是11716.说明线程使用的是进程的资源。
threading.current_thread().name方法会返回该线程的名字,如果该线程在创建的时候没有设置名字,则默认为Thread-1、Thread-2........
这里有一个小细节需要注意。请看如下创建线程代码。
def thread_test():print("[TEST]:%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < 5:cnt += 1print("[TEST]:%s cnt:%s" %(threading.current_thread().name,cnt))time.sleep(1)if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))t = threading.Thread(target=thread_test(), name='test_thread') #创建一个线程,线程名字为test_thread。print("thread start")t.start()print("wait thread end")t.join()print("thread end")
结果如下:
可以看到,在创建完线程后,线程自动启动了,并且主程序阻塞了。另外创建的线程名字并不是设置的“test_thread”。
主要是因为在创建线程的时候,target参数多了一个(),此时系统会认为这不是一个线程,而是一个任务,就去调用,从而阻塞主程序。所以,在创建线程的时候一定要注意,target参数是不带()的。
2.多线程
为什么要使用多线程
- 线程在程序中是独立的,并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存,文件句柄和其他进程应有的状态。
- 线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,从而极大的提升了程序的运行效率
- 线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。
多线程的优点
- 进程之间不能共享内存,但线程之间共享内存非常容易
- 操作系统在创建进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能要高得多。
- Python内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Python的多线程编程。
见例程:
def thread_test(param):print("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < param:cnt += 1print("[%s]:cnt:%s" %(threading.current_thread().name,cnt))if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))t1 = threading.Thread(target=thread_test, name='test_thread1' ,args=(10,)) #创建一个线程,线程名字为test_thread1。t2 = threading.Thread(target=thread_test, name='test_thread2', args=(10,)) # 创建一个线程,线程名字为test_thread2。t3 = threading.Thread(target=thread_test, name='test_thread3', args=(10,)) # 创建一个线程,线程名字为test_thread3。t1.start()t2.start()t3.start()t1.join()t2.join()t3.join()print("thread end")
结果:
这里创建了三个线程,线程名为test_thread1、test_thread2、test_thread3.运行的内容都是一样的。可以看到,三个任务是穿插着在运行,“并行”运行。
线程间的数据共享
已知进程见的数据是没办法共享的,那线程如何呢?见如下代码:
g_count = 1000def thread_test(param):global g_countprint("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < param:g_count -= 1cnt += 1print("[%s]:cnt:%s,g_cnt:%s" %(threading.current_thread().name,cnt,g_count))def thread_test3(param):print("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < param:cnt += 1print("[%s]:cnt:%s,g_cnt:%s" %(threading.current_thread().name,cnt,g_count))if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))t1 = threading.Thread(target=thread_test, name='test_thread1' ,args=(10,)) #创建一个线程,线程名字为test_thread1。t2 = threading.Thread(target=thread_test, name='test_thread2', args=(10,)) # 创建一个线程,线程名字为test_thread2。t3 = threading.Thread(target=thread_test3, name='test_thread3', args=(10,)) # 创建一个线程,线程名字为test_thread3。t1.start()t2.start()t3.start()t1.join()t2.join()t3.join()print("thread end")
结果:
可以看到,线程3打印的数据,在随着线程1和2的操作在不断变化,说明,全局变量在线程之间是共享的。
3.线程锁
因为一个全局变量可以被所以线程共享使用,那就无可避免的会出现抢占使用的情况。比如A线程正在调用函数进行数据计算,计算到一半,此时线程B进入,开始进行数据计算。那当调度器再次回到A这边的时候,其中的变量已经发生了改变,那么最终计算出来的结果也就是异常的。
见如下代码:
def thread_test(param):global g_countprint("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < param:value_cal(cnt)cnt += 1if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))t1 = threading.Thread(target=thread_test, name='test_thread1' ,args=(1000000,)) #创建一个线程,线程名字为test_thread1。t2 = threading.Thread(target=thread_test, name='test_thread2', args=(1000000,)) # 创建一个线程,线程名字为test_thread2。t1.start()t2.start()t1.join()t2.join()print("thread end")print("g_count:%d" % g_count)
结果:
可以看到,在调用函数时,只是进行加减相同的数。那么理论上最终结果是跟原来的数据相同。而目前计算出来的结果确实一个负值。原因就是线程之间没有进行锁保护,导致在多线程同时使用函数时,变量变化异常导致的。
为了解决这个问题,Python内置了Lock模块。该模块将会产生一个锁,在锁有效的情况下,其他线程是无法调用被保护的代码段。以此来解决重复调用的问题。
acquire()是Python中线程模块的Lock类的内置方法。此方法用于获取锁,阻塞或非阻塞。
函数原型:
def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ...
参数:
blocking:可选参数,用于阻塞标志。如果设置为True,调用线程将在其他线程持有该标志时被阻塞。一旦该锁被释放,调用线程将获取该锁并返回True。如果设置为False,如果其他线程已获取锁,则不会阻塞该线程,并返回False。
timeout:可选参数。表示阻塞线程的超时时间,单位为秒。默认为-1.
如果没有参数的情况下,会阻塞调用的线程,直到被解锁。
见如下代码:
g_count = 1000lock = threading.Lock()def value_cal(n):global g_countlock.acquire(True)try:g_count += ng_count -= nfinally:lock.release()def thread_test(param):global g_countprint("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0while cnt < param:value_cal(cnt)cnt += 1# print("[%s]:cnt:%s,g_cnt:%s" %(threading.current_thread().name,cnt,g_count))if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread().name,os.getpid()))t1 = threading.Thread(target=thread_test, name='test_thread1' ,args=(1000000,)) #创建一个线程,线程名字为test_thread1。t2 = threading.Thread(target=thread_test, name='test_thread2', args=(1000000,)) # 创建一个线程,线程名字为test_thread2。t1.start()t2.start()t1.join()t2.join()print("thread end")print("g_count:%d" % g_count)
结果:
可以看到,在加了锁之后,程序的结果就是我们预期的结果。
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码。其他线程将会阻塞等待,直到获取到锁为止。
锁的好处就是确保了某段代码只能由一个线程从头到尾完整地执行。坏处就是阻止了多线程并发执行,效率降低了。此外,如果锁没有被及时释放,可能会导致死锁。
注:在调用acquire后,一定要调用release进行释放,否则线程会一直阻塞,最终造成程序死锁。只能通过系统强制终止。
4.多线程变量管理
在使用多线程的情况下,每个线程都有自己的数据需要使用。如果所有线程都使用全局变量的话,那么所有在调用全局变量的地方都需要加上锁。这样对于整个程序运行来说无疑会增加过多的负担。那如果使用局部变量的话,在每个函数调用的时候,变量都需要作为传参传入到函数中。这样传递起来也很麻烦。所以,Python提供了ThreadLocal模块。
threading.local()会产生一个全局变量,任意一个线程都可以调用。但是每个线程调用这个变量后,就变成了局部变量。对于其他线程来说是不可更改的。
见如下代码:
local = threading.local()def value_cal(n):if n == 0:local.abs -= 1else:local.abs += 1def thread_test(param):print("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0local.abs = 1000while cnt < param:value_cal(0)cnt += 1print("[%s]:g_count:%d" % (threading.currentThread().name,local.abs))def thread_test2(param):print("[%s]:running,pid:%s" %(threading.currentThread().name,os.getpid()))cnt = 0local.abs = 1000while cnt < param:value_cal(1)cnt += 1print("[%s]:g_count:%d" % (threading.currentThread().name,local.abs))if __name__ == '__main__':print("%s is running,pid:%s" %(threading.currentThread(),os.getpid()))t1 = threading.Thread(target=thread_test, name='test_thread1' ,args=(100,)) #创建一个线程,线程名字为test_thread1。t2 = threading.Thread(target=thread_test2, name='test_thread2', args=(100,)) # 创建一个线程,线程名字为test_thread2。t1.start()t2.start()t1.join()t2.join()print("thread end")
结果:
可以看到,两个线程都使用了local.abs这个变量,并且在调用中也都对这个变量进行了操作。但是最终是local.abs变量与线程向对应。并没有被另外一个线程所影响。
全局变量local就是一个ThreadLoacl对象,每个线程都可以对其进行读写,但是互不影响。也可以把local看成是全局变量,但是local.abs是线程的局部变量,可以任意读写而互不干扰,也不用管理锁问题,ThreadLoacl内部会处理。
Python学习七(线程)相关推荐
- Python+大数据-Python学习(七)
Python+大数据-Python学习(七) 1.文件的基本操作 文件打开的格式: file = open(文件路径,读写模式) - open默认打开的式r模式 文件路径:可以写相对路径,也可以写 ...
- Python学习:线程池原理及实现
传统多线程方案会使用"即时创建, 即时销毁"的策略.尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于 ...
- Python学习之==线程进程
一.什么是线程(thread) 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一个线程指的是进程中一个单一顺序的控制流,一个进程中可以包含多个线程,每条线程并行 ...
- Python学习(七) 流程控制if语句
在Python中流程控制if语句采用如下格式: if expression : statement elif expression : statement elif expression : stat ...
- Python学习笔记:线程和进程(合),分布式进程
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python学习笔记:进程和线程(承)
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- python sorted下标_Python学习教程(Python学习路线):第七天-字符串和常用数据结构
Python学习教程(Python学习路线):字符串和常用数据结构 使用字符串 第二次世界大战促使了现代电子计算机的诞生,当初的想法很简单,就是用计算机来计算导弹的弹道,因此在计算机刚刚诞生的那个年代 ...
- Python学习教程(Python学习路线):Day13-进程和线程
Python学习教程(Python学习路线):进程和线程 今天我们使用的计算机早已进入多CPU或多核时代,而我们使用的操作系统都是支持"多任务"的操作系统,这使得我们可以同时运行多 ...
- Python学习,第七课(灵活使用Frame,让布局更舒适)
Python学习第七课(让界面布局舒适,是一个长久的工作) 一入布局深似海,加一减一都很难 基础知识 尝试布局 尝试好布局,感受下元素带来的不同 改造主程序,细节还是要优化 细节优化,细枝末节的参数 ...
最新文章
- SAP FI 会计凭证过账bapi BAPI_ACC_DOCUMENT_POST
- html vue分页,Vue.js bootstrap前端实现分页和排序
- 深入理解支持向量机(SVM)
- maven引用外部jar依赖
- 如何将本地项目提交到git服务器中
- oracle中的日期加减,ORACLE 日期加减函数
- 55.Linux/Unix 系统编程手册(下) -- 文件加锁
- Canvas画布进阶篇---绘制文本
- FireMonkey TListView的用法二:ItemAppearance为客制化外观Custom
- MovieLens 数据集补充版爬虫代码解析
- C程序的基本组成结构
- 数字电路时钟无毛刺切换
- 读【选修计算机专业的伤与痛】
- html5人脸拼图,面向眼机交互的界面控件设计方法研究.pdf
- 语c和c语言,00后黑话等级测试,你能看懂几句?
- 如何预防钓鱼邮件?S/MIME邮件证书来支招
- 开源商城WSTMart支付开发研究[转]
- 联想笔记本PE启动热键
- 未来5年IT产业及网络市场发展趋势分析
- H264(AVC)/H265(HEVC)/H266(VVC):GOP的区别
热门文章
- 基于SpringMVC、Html5 的图表展示
- 东南大学计算机网络报告,东南大学计算机网络实验报告一_调查报告_表格模板_实用文档.doc...
- 计算机主板故障检测装置,一种计算机主板的故障检测装置
- 《预训练周刊》第42期: 通用模型、训练计算优化、多模态训练
- 如何将视频转换为GIF动图?三种方法轻松搞定!
- python正则表达式开头和结尾_Python 基础之正则之一 单字符,多字符匹配及开头结尾匹配...
- 免费宾馆软件 JDPMS
- 栈空间内存和堆空间内存
- request模块(转载)
- matlab绘图画八卦,我这里有十条画图秘籍,让你不再「灵魂画手」