Python的多线程,守护线程,线程安全

简介: Python的多线程,守护线程,线程安全

1、线程

在python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另一种形式

线程是进程中执行代码的一个分支,每个执行分支(线程),要想工作执行代码,需要CPU进行调度。

线程是CPU调度的基本单位,每个进程都最少有一个线程,而这个线程就是我们常说的主线程

(1)线程的作用

多线程可以完成多任务

程序默认会有一个主线程,程序员自己创建的线程可以成为子线程,多线程可以完成多任务

多线程,多个线程是在一个进程里创建的,多线程资源共享,共享全局变量

#线程的缘起

资源分配需要分配内存空间,分配cpu:

分配的内存空间存放着临时要处理的数据等,比如要执行的代码,数据

而这些内存空间是有限的,不能无限分配

目前配置高的主机,5万个并发已是上限.线程概念应用而生.

(2)线程的特点

线程是比较轻量级,能干更多的活,一个进程中的所有线程资源是共享的.

一个进程至少有一个线程在工作

一个进程里包含了多个线程,线程之间是异步并发

进程实际上是不干活的,线程才是干活的那一位

进程既可以并发,也可以并行

线程只能并发,不能并行

同一时间,一个进程中的多条线程只能被一个CPU执行

(3)线程的缺陷

#python中的线程可以并发,但是不能并行(同一个进程下的多个线程不能分开被多个cpu同时执行)

#原因:

全局解释器锁(Cpython解释器特有) GIL锁:

同一时间,一个进程下的多个线程只能有一个被cpu执行,不能实现线程的并行操作

python是解释型语言,执行一句编译一句,而不是一次性全部编译成功,不能提前规划,都是临时调度

容易造成cpu执行调度异常.所以加了一把锁叫GIL

#想要并行的解决办法:

(1)用多进程间接实现线程的并行,并不理想,开辟空间消耗资源

(2)换一个Pypy,Jpython解释器 也不好用,兼容性问题

#程序分为计算密集型和io密集型

对于计算密集型程序会过度依赖cpu,但网页,爬虫,OA办公,这种io密集型的程序里,python多线程绰绰有余

计算密集型,Python比较吃力

1、GIL锁不是python的特点。而是cpython的特点。

2、在cpython解释器中,GIL是一把互斥锁,用来保证进程中同一个时刻只有一个线程在执行。

3、在没有GIL锁的情况下,有可能多线程在执行一个代码的同时,垃圾回收机制对所执行代码的变量直接进行回收,其他的线程再使用该变量时会导致运行错误。

4、总结

对于IO密集型应用,即便有GIL存在,由于IO操作会导致GIL释放,其他线程能够获得执行权限。由于多线程的通讯成本低于多进程,因此偏向使用多线程。

对于计算密集型应用,由于CPU一直处于被占用状态,GIL锁直到规定时间才会释放,然后才会切换状态,导致多线程处于绝对的劣势,此时可以采用多进程+协程。

进程是资源分配的最小单元

线程是cpu执行调度的最小单元

Python先发明的线程,后来添加的进程。所以线程的使用方法与进程一样

#(1) 一个进程里包含了多个线程,线程之间是异步并发

from threading import Thread
from multiprocessing import Process
import os , time , random


def func(i):
    time.sleep(random.uniform(0.1,0.9))
    print("当前进程号:{}".format(os.getpid()) , i)
    
if __name__ == "__main__":     #线程里面这句话可以不加
    for i in range(10):
        t = Thread(target=func,args=(i,))
        t.start()
        
print(os.getpid())

如下可见,子线程中的进程号与主进程号一样,并且子线程和主进程之间是异步并发的

#(2) 并发的多进程和多线程之间,多线程的速度更快

#多线程速度
def func(i):
    print( "当前进程号:{} , 参数是{} ".format(os.getpid() , i)  )


if __name__ == "__main__":
    lst = []
    startime = time.time()
    for i in range(10000):
        t = Thread(target=func,args=(i,))
        t.start()
        lst.append(t)
    # print(lst)
    for i in lst:
        i.join()
    endtime = time.time()
    print("运行的时间是{}".format(endtime - startime) ) # 运行的时间是1.8805944919586182

#多进程速度

if __name__ == "__main__":
    lst = []
    startime = time.time()
    for i in range(10000):
        p = Process(target=func,args=(i,))
        p.start()
        lst.append(p)
    # print(lst)
    for i in lst:
        i.join()
    endtime = time.time()
    print("运行的时间是{}".format(endtime - startime) ) # 运行的时间是101.68004035949707

多进程:1000个进程耗时9.06秒

多线程:多线程之间,数据共享,可以直接操作数据。1000个线程耗时0.087秒,差距巨大,所以对于IO密集型,还是用多线程较快

多进程执行计算密集型,如果各个进程间各计算各的,不用共享数据,由于可以使用多核,比多线程快。

如果是各个进程间数据共享,同步计算最终结果,多进程反而非常慢,远远慢于多线程

#(3) 多线程之间,数据共享

num = 100
lst = []
def func():
    global num
    num -= 1

for i in range(100):
    t = Thread(target=func)
    t.start()
    lst.append(t)
    
for i in lst:
    i.join()
    
print(num)

多线程之间共享数据,可以直接操作

2、自定义线程类

#用类定义线程,必须手动调用父类__init__方法,必须重载父类run方法,定义自己线程类逻辑

from threading import Thread

import os,time

(1)必须继承父类Thread,来自定义线程类

class MyThread(Thread):

    def __init__(self,name):
        # 手动调用父类的构造方法
        super().__init__()
        # 自定义当前类需要传递的参数
        self.name = name

    def run(self):
        print(  "当前进程号{},name={}".format(os.getpid() , self.name)  )

if __name__ == "__main__":
    t = MyThread("我是线程")
    t.start()
    print( "当前进程号{}".format(os.getpid()) )

新版:

#自定义线程类

from threading import Thread,current_thread
# (1)必须继承父类Thread,来自定义线程类
class MyThread(Thread):
    def __init__(self,name):
        # 手动调用父类的构造方法
        super().__init__()
        # 自定义当前类需要传递的参数
        self.name = name

    def run(self):
        print(f"当前线程的线程号是{current_thread().ident}")


if __name__ == '__main__':
    for i in range(10):
        t = MyThread(f"线程{i}")
        t.start()

(2)线程中的相关属性

# 线程.is_alive()    检测线程是否仍然存在
# 线程.setName()     设置线程名字
# 线程.getName()     获取线程名字
# 1.currentThread().ident 查看线程id号  新版用current_thread().ident
# 2.enumerate()        返回目前正在运行的线程列表
# 3.activeCount()      返回目前正在运行的线程数量

getName,setName被弃用了

新版:

def func():
    time.sleep(1)
if __name__ == "__main__":
    t = Thread(target=func)
    t.start()
    # 检测线程是否仍然存在
    print( t.is_alive() )
    # 线程.getName()     获取线程名字
    print(t.getName())
    # 设置线程名字
    t.setName("抓API接口")
    print(t.getName())



from threading import currentThread
from threading import enumerate
from threading import activeCount
def func():
    time.sleep(0.1)
    print("当前子线程号id是{},进程号{}".format( currentThread().ident ,os.getpid()) )

if __name__ == "__main__":
    t = Thread(target=func)
    t.start()
    print("当前主线程号id是{},进程号{}".format( currentThread().ident ,os.getpid()) )

    
    for i in range(5):
        t = Thread(target=func)
        t.start()
    # 返回目前正在运行的线程列表
    lst = enumerate()
    print(lst,len(lst))
    # 返回目前正在运行的线程数量 (了解)
    print(activeCount())

新版:

# ### 线程中的相关属性
import time
from threading import Thread,current_thread
from threading import enumerate
from threading import active_count

def func():
    time.sleep(1)
    print(f"当前子线程的线程号{current_thread().ident}")

if __name__ == '__main__':
    for i in range(5):
        t = Thread(target=func)
        t.start()
        #设置线程名
        t.name = f"抓API接口{i}"

    # 返回目前正在运行的线程列表
    lst = enumerate()
    print(lst,len(lst))
    # 返回目前正在运行的线程数量 (了解)
    print(active_count())

当前运行线程数量,一个主线程,5个子线程

3、守护线程

等待所有线程全部执行完毕之后,自己再终止程序,守护所有线程

from threading import Thread
import time
def func1():
    while True:
        time.sleep(1)        
        print("我是函数func1")
        
def func2():
    print("我是func2  start ... ")
    time.sleep(3)
    print("我是func2  end ... ")
    
def func3():
    print("我是func3 start ... ")
    time.sleep(6)    
    print("我是func3  end ... ")
    
    
if __name__ == "__main__":
    t = Thread(target=func1)
    t2 = Thread(target=func2)
    t3 = Thread(target=func3)
    
    # 设置守护线程 (启动前设置)  新版本该方法已弃用
    t.setDaemon(True)
    
    t.start()
    t2.start()
    t3.start()
    
    print("主线程执行结束.... ")

新版本设置守护线程的setDaemon()方法已弃用,改为了通过属性设置

守护线程是等所有线程执行结束,自己再终止程序,守护所有线程

4、线程中的数据安全问题

#多线程之间共享全局变量,出现数据错误问题

import threading

#全局变量
g_sum = 0


def sum1():
    for i in range(1000000):
        # 不可变类型用方法修改数据,要声明为全局变量
        global g_sum
        g_sum += 1
    print('sum1', g_sum)


def sum2():
    for i in range(1000000):
        global g_sum
        g_sum += 1
    print('sum2', g_sum)


if __name__ == '__main__':
    sum1_thread = threading.Thread(target=sum1)
    sum2_thread = threading.Thread(target=sum2)
    sum1_thread.start()
    sum2_thread.start()

sum1 911662

sum2 1434503

出现的结果不是2百万,由于两个线程是随机执行的,有可能几乎同时对数据进行操作,导致少加的问题

解决办法:上锁

互斥锁:

互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作

注意:

互斥锁是多个线程一起去抢,抢到锁的线程先执行,没抢到锁的线程需要等待,等互斥锁使用完释放后,

其他等待的线程再去抢这个锁

线程不安全,执行结果不对

多个线程可能同时操作数据,拿到的数据一样,操作后的结果不一样。造成混乱。比如几乎同时拿到1,运算需要时间,但不同线程读取得到的数据是一样的。

线程1拿到数据加1,得到2,线程2拿到数据减1,得到0

线程1往原来数据存2,线程2往原来存0,以后面放的为准,下次操作又会出现同样的混乱。

上锁:不同的线程采用同步计算,当一个线程运算完,才释放锁,让另一个线程运算

from threading import  Thread  , Lock
import time

n = 0

def func1(lock):
    global n
    
    lock.acquire() 
    for i in range(1000000):        
        n += 1
    lock.release()

def func2(lock):
    global n
    # with语法可以简化上锁+解锁的操作,自动完成
    with lock:
        for i in range(1000000):
            n -= 1

        
if __name__ == "__main__":
    lst = []
    lock = Lock()
    
    start = time.time()
    for i in range(10):
        t1 = Thread(target=func1 ,args=(lock,) )
        t1.start()
        
        t2 = Thread(target=func2 ,args=(lock,) )
        t2.start()
        
        lst.append(t1)
        lst.append(t2)

    for i in lst:
        i.join()
    # print(lst,len(lst))
    end = time.time()
    print("主线程执行结束... 当前n结果为{} ,用时{}".format(n , end-start))

给线程上锁,执行结果准确 。with语法,可以简化上锁,解锁操作,自动完成


相关文章
|
5天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
34 20
|
18天前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
97 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
2月前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
64 2
|
2月前
|
监控 Java 开发者
Java线程管理:守护线程与本地线程的深入剖析
在Java编程语言中,线程是程序执行的最小单元,它们可以并行执行以提高程序的效率和响应性。Java提供了两种特殊的线程类型:守护线程和本地线程。本文将深入探讨这两种线程的区别,并探讨它们在实际开发中的应用。
47 1
|
3月前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
23天前
|
存储 缓存 Java
Python高性能编程:五种核心优化技术的原理与Python代码
Python在高性能应用场景中常因执行速度不及C、C++等编译型语言而受质疑,但通过合理利用标准库的优化特性,如`__slots__`机制、列表推导式、`@lru_cache`装饰器和生成器等,可以显著提升代码效率。本文详细介绍了这些实用的性能优化技术,帮助开发者在不牺牲代码质量的前提下提高程序性能。实验数据表明,这些优化方法能在内存使用和计算效率方面带来显著改进,适用于大规模数据处理、递归计算等场景。
58 5
Python高性能编程:五种核心优化技术的原理与Python代码
|
2月前
|
Python
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
52 14
|
2月前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
116 2
|
2月前
|
Unix Linux 程序员
[oeasy]python053_学编程为什么从hello_world_开始
视频介绍了“Hello World”程序的由来及其在编程中的重要性。从贝尔实验室诞生的Unix系统和C语言说起,讲述了“Hello World”作为经典示例的起源和流传过程。文章还探讨了C语言对其他编程语言的影响,以及它在系统编程中的地位。最后总结了“Hello World”、print、小括号和双引号等编程概念的来源。
126 80
|
2月前
|
存储 数据采集 人工智能
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。

热门文章

最新文章

推荐镜像

更多