Python最详细的零基础入门之——多线程详解!

进程 && 线程

进程:是内存中的一个独立的句柄,我们可以理解为一个应用程序在内存中就是一个进程。 各个进程之间是内存相互独立,不可共享的

线程:每个应用运行之后就会对应启动一个主线程,通过主线程可以创建多个字线程,各个线程共享主进程的内存空间。

关于线程、进程的解释有一篇有趣而生动的解释(http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html)

GIL(全局解释器锁)

我们知道多进程(mutilprocess) 和 多线程(threading)的目的是用来被多颗CPU进行访问, 提高程序的执行效率。 但是在python内部存在一种机制(GIL),在多线程 时同一时刻只允许一个线程来访问CPU。

GIL 并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。

Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把 GIL 归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

虽然python支持多线程,但是由于GIL的限制,在实际运行时,程序运行后开启多个线程,但在通过GIL后同时也只能有一个线程被CPU执行。

多线程

1)多线程执行方法

import time
from threading import Thread
def do_thread(num):
 print("this is thread %s" % str(num))
 time.sleep(3)
for i in range(5):
 t = Thread(target=do_thread, args=(i,))
 t.start()

以上方法就开启了一个5个线程,target用来定义开启线程后要执行的方法,args为参数

线程的其它方法:

1 setName(), getName()

setName(): 给线程设置一个名字

getName(): 获取线程的名称

import time
from threading import Thread
def do_thread(num):
 print("this is thread %s" % str(num))
 time.sleep(3)
for i in range(2):
 t = Thread(target=do_thread, args=(i,))
 t.start()
 t.setName("Mythread_{0}".format(str(i)))
 print(t.getName())
run result:
 this is thread 0
 Mythread_0
 this is thread 1
 Mythread_1

2 setDaemon()

setDaemon(True/False): 设置创建的子线程为前台线程或后台线程.设置为True则子线程为后台线程。线程默认为前台线程(不设置此方法)

前台线程: 当子线程创建完成后,主线程和子线程(前台线程)同时运行,如果主线程执行完成,而子线程还未完成则等待子线程执行完成以后整个程序才结束。

后台线程: 当子线程创建完成后,如果子线程还未结束,而主线程运行结束则不管子线程了,程序就结束。

此方法设置必须在 start() 方法前进行设置, 看代码:

import time
from threading import Thread
def do_thread(num):
 print("this is thread %s" % str(num))
 time.sleep(3)
 print("OK", str(num))
for i in range(2):
 t = Thread(target=do_thread, args=(i,))
 # 不设置此方法默认前台线程,
 #t.setDaemon(True)
 t.setName("Mythread_{0}".format(str(i)))
 t.start()
 print(t.getName())
run result:
this is thread 0
Mythread_0
this is thread 1
Mythread_1
OK 0
OK 1
import time
from threading import Thread
def do_thread(num):
 print("this is thread %s" % str(num))
 time.sleep(3)
 # 执行到此时主线程执行完了,程序结束,下面的代码不会执行
 print("OK", str(num))
for i in range(2):
 t = Thread(target=do_thread, args=(i,))
 # 设置线程为后台线程
 t.setDaemon(True)
 t.setName("Mythread_{0}".format(str(i)))
 t.start()
 print(t.getName())
run result:
this is thread 0
Mythread_0
this is thread 1
Mythread_1

3 join()

join(timeout) : 多线程的 wait(),当主线程执行 子线程.join() 方法后,主线程将等待子线程执行完再接着执行。当加上timeout参数后,如果超过timeout时间不管子线程有没有执行完都将结束等待

看下面两个例子

import time
from threading import Thread
def do_thread(num):
 time.sleep(3)
 print("this is thread %s" % str(num))
for i in range(2):
 t = Thread(target=do_thread, args=(i,))
 t.setName("Mythread_{0}".format(str(i)))
 t.start()
 print("print in main thread: thread name:", t.getName())
run result:
 print in main thread: thread name: Mythread_0
 print in main thread: thread name: Mythread_1
 this is thread 0
 this is thread 1

上面无join方法时,主线程执行完print,等待子线程函数中的print执行完成,这个程序退出。 下面我们看看加上join方法后的效果

import time
from threading import Thread
def do_thread(num):
 time.sleep(3)
 print("this is thread %s" % str(num))
for i in range(2):
 t = Thread(target=do_thread, args=(i,))
 t.setName("Mythread_{0}".format(str(i)))
 t.start()
 t.join()
 print("print in main thread: thread name:", t.getName())
run result:
this is thread 0
print in main thread: thread name: Mythread_0
this is thread 1
print in main thread: thread name: Mythread_1

当程序运行到join后,将等待子程序执行完成,然后才向下执行。这样真个程序就变成一个单线程的顺序执行了。多线程就没什么鸟用了。

join()与setDaemon()都是等待子线程结束,有什么区别呢:

当执行join()后主线程就停了,直到子线程完成后才开始接着主线程执行,整个程序是线性的

setDaemon() 为前台线程时,所有的线程都在同时运行,主线程也在运行。只不过是主线程运行完以后等待所有子线程结束。这个还是一个并行的执行,执行效率肯定要高于join()方法的。

4 线程锁

线程是内存共享的,当多个线程对内存中的同一个公共变量进行操作时,会导致线程争抢的问题,为了解决此问题,可以使用线程锁。

import time
import threading
def do_thread(num):
 global public_num
 # 加锁
 lock.acquire()
 public_num -= 1
 # 解锁
 lock.release()
 time.sleep(1)
 print("public_num in thread_%s is %s" % (str(num), str(public_num)))
public_num = 100
threads_list = []
lock = threading.Lock()
for i in range(50):
 t = threading.Thread(target=do_thread, args=(i,))
 t.setName("Mythread_{0}".format(str(i)))
 t.start()
 threads.append(t)
 # 等待所有子线程结束
for t in threads:
 t.join()
print("last result of public_num is ", public_num)

5 event()

线程的事件, 用于主线程控制子线程的执行。它的本质就是定义了一个全局的flag标识,并通过一些方法来获取、设置此标识。包括:

wait()方法:当flag标识为False时,wait()方法将阻塞,为True时,wait()不阻塞

set()方法:设置flag标识为True

clear()方法: 设置flag标识为False

初始化时flag标识为False(阻塞状态)

is_set()/isSet() : 判断当前flag标识是否为True

import threading
def do(event):
 print(‘start‘)
 # 默认初始化状态为False,到这里就阻塞了
 event.wait()
 print(‘execute\n‘)
if __name__ == "__main__":
 event_obj = threading.Event()
 for i in range(10):
 t = threading.Thread(target=do, args=(event_obj,))
 t.start()
 inp = input(‘input:‘)
 if inp == ‘true‘:
 # 如果为true,则flag=True,不阻塞,子进程继续运行
 event_obj.set()
 else:
 event_obj.clear()

event一个模拟红绿灯的实例:

def light():
 linght_time = 0
 if not event.is_set():
 event.set() # Flag = True, 阻塞
 while True:
 time.sleep(1)
 if linght_time < 10:
 print("Green is on....")
 elif linght_time < 13:
 print("Yellow is on ....")
 elif linght_time < 16:
 print("Red is on ......")
 if event.is_set():
 event.clear()
 else: # 大于16, 该重新调绿灯了
 linght_time = 0
 event.set()
 linght_time += 1
def car_run(carnum):
 while True:
 time.sleep(2)
 if event.is_set():
 print("car %s is run" % carnum)
 else:
 print("CAR %s IS WAITTING........" % carnum)
if __name__ == "__main__":
 event = threading.Event()
 l = threading.Thread(target=light, )
 l.start()
 for i in range(3):
 c = threading.Thread(target=car_run, args=(str(i), ))
 c.start()

6) Semaphore()

Semaphore信号量管理一个内置的计数器:

每当调用acquire()时内置计数器-1;

调用release() 时内置计数器+1;

计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

import threading
import time
def do():
 semaphro.acquire()
 print("this is {0} set the semaphore".format(threading.current_thread().getName()))
 time.sleep(2)
 semaphro.release()
 print("\033[1;30mthi is {0} release the semaphore\033[0m".format(threading.current_thread().getName()))
if __name__ == "__main__":
 semaphro = threading.Semaphore(2)
 for i in range(10):
 t = threading.Thread(target=do)
 t.setName("Thread_{0}".format(str(i)))
 t.start()
 print("finished")

上例中,虽然创建了10个线程,但同时只有2个线程在运行,就是因为在线程中通过Semaphore设置了2个信号量。只有其中一个释放后另其它的线程再能开始执行

欢迎关注我的公众号:Python学习交流,有福利相送哦!

欢迎加入我的千人交流学习答疑群:125240963

原文地址:https://www.cnblogs.com/Python1234/p/9112666.html

时间: 2024-08-30 14:56:36

Python最详细的零基础入门之——多线程详解!的相关文章

零基础入门Python3-函数详解(1)

我们通过一种运算方法,把一个数字运算成另一个数字,每次需要运算的数字都是不同的,但是运算方法都是一致的.如果每次运算都需要定义和编写相同代码,那我们的工作量简直太大了.函数就是对应的解决办法,输入对象,通过相同的计算,得到相应的结果,这个调用过程就叫做函数. 1.定义函数 def 函数名称():         语句1         语句2         ......         return xxx def是定义函数的标识,这个是不能丢的.然后函数名称和变量定义的规则是一致的,不能以数

【Python教程】《零基础入门学习Python》(小甲鱼)

[Python教程]<零基础入门学习Python>(小甲鱼) 讲解通俗易懂,诙谐. 哈哈哈. https://www.bilibili.com/video/av27789609 原文地址:https://www.cnblogs.com/F4NNIU/p/9765629.html

鱼C《零基础入门学习Python》10-17节课时知识点总结

第10讲:列表:一个打了激素的数组 1. 列表都可以存放一些什么东西?  我们说 Python 的列表是一个打了激素的数组,如果把数组比喻成集装箱,那么 Python 的列表就是一个大仓库,Ta 可以存放我们已经学习过的任何数据类型. 2. 向列表增加元素有哪些方法?  三种方法想列表增加元素,分别是:append().extend() 和 insert().    3. append() 方法和 extend() 方法都是向列表的末尾增加元素,请问他们有什么区别?  append() 方法是将

鱼C《零基础入门学习Python》1—9节课时知识点总结

第一节:我和python的第一次亲密接触 0. Python 是什么类型的语言? 答:脚本语言(Scripting language)是电脑编程语言,因此也能让开发者藉以编写出让电脑听命行事的程序.以简单的方式快速完成某些复杂的事情通常是创造脚本语言的重要原则,基于这项原则,使得脚本语言通常比 C语言.C++语言 或 Java 之类的系统编程语言要简单容易.也让脚本语言另有一些属于脚本语言的特性: 语法和结构通常比较简单 学习和使用通常比较简单 通常以容易修改程序的“解释”作为运行方式,而不需要

大牛整理最全Python零基础入门学习资料

大牛整理最全Python零基础入门学习资料 0 发布时间:『 2017-11-12 11:56 』     帖子类别:『人工智能』  阅读次数:3504 (本文『大牛整理最全Python零基础入门学习资料』的责任编辑:老王) 摘要:大牛整理最全Python零基础入门学习资料 Python数据类型--数字 Python Number 数据类型用于存储数值. 数据类型是不允许改变的,这就意味着如果改变 Number 数据类型的值,将重新分配内存空间. var1 = 1 var2 = 10 您也可以使

Cloudera Manager、CDH零基础入门、线路指导 http://www.aboutyun.com/thread-9219-1-1.html (出处: about云开发)

Cloudera Manager.CDH零基础入门.线路指导http://www.aboutyun.com/thread-9219-1-1.html(出处: about云开发) 问题导读:1.什么是cloudera CM .CDH?2.CDH.CM有哪些版本?3.CDH.CM有哪些安装方式?4.CDH如何开发? <ignore_js_op> 我们知道cloudera CDH 是为简化hadoop的安装,也对对hadoop做了一些封装.那么我们就像尝试学习cloudera.cloudera本质h

Linux及Arm-Linux程序开发笔记(零基础入门篇)

Linux及Arm-Linux程序开发笔记(零基础入门篇)  作者:一点一滴的Beer http://beer.cnblogs.com/ 本文地址:http://www.cnblogs.com/beer/archive/2011/05/05/2037449.html 目录 一.Arm-Linux程序开发平台简要介绍... 3 1.1程序开发所需系统及开发语言... 3 1.2系统平台搭建方式... 4 二.Linux开发平台搭建... 5 2.1安装虚拟工作站... 5 2.2安装Linux虚拟

零基础入门jQuery视频教程

零基础入门jQuery最新版开发.NET富客户端应用(选择器.DOM操作.事件和动画.Ajax应用.插件.Mobile)课程分类:.NET+Jquery适合人群:初级课时数量:35课时用到技术:javascript,ajax,jquery,handler涉及项目:各知识点的项目案例和名为JaneShop的品牌服装和包包的购物网站咨询qq:1840215592 零基础入门jQuery视频教程详细查看:http://www.ibeifeng.com/goods-425.html 零基础入门jQuer

大数据系统学习零基础入门到精通加项目实战2017最新全套视频教程

38套大数据,云计算,架构,数据分析师,Hadoop,Spark,Storm,Kafka,人工智能,机器学习,深度学习,项目实战视频教程 视频课程包含: 38套大数据和人工智能精品高级课包含:大数据,云计算,架构,数据挖掘实战,实时推荐系统实战,电视收视率项目实战,实时流统计项目实战,离线电商分析项目实战,Spark大型项目实战用户分析,智能客户系统项目实战,Linux基础,Hadoop,Spark,Storm,Docker,Mapreduce,Kafka,Flume,OpenStack,Hiv