python 线程笔记一 (概念+示例代码)

1. 线程的概念

       线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。线程是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属于一个进程的其它线程共享进程所拥有的全部资源。

1.1 主线程

       当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,简而言之:程序启动就会创建一个主线程。

      主线程的重要性体现在两个方面:

(1)主线程是可以产生其它子线程的线

(2)通常它必须最后完成执行,比如执行各种关闭动作

1.2 子线程

     可以看做是程序执行的一条分支,当子线程启动后会和主线程一起同时执行。

1.3 单线程与多线程基本示例代码

单线程:

import time
 
def sayHello():
    print("Hello")
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        sayHello()

结果:(耗费约5s时间)

多线程:

import time,threading
 
def sayHello():
    print("Hello")
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=sayHello)
 
        # 启动子线程对象
        thread_obj.start()

结果:(耗费约1s时间)

1.4 主线程会等待所有的子线程结束后才结束
import time,threading
 
def sing():
    for i in range(3):
        print("is singing... %d" % i)
        time.sleep(1)
 
def dance():
    for i in range(3):
        print("is dancing... %d" % i)
        time.sleep(1)
 
 
if __name__ == '__main__':
    print("The main thread starts to execute")
    
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
 
    t1.start()
    t2.start()
    
    print("Main thread execution completed")

结果:(可以看到主线程执行完毕后,在等待所有线程执行完毕后才结束运行)

2. 线程的数量

        使用threading.enumerate()可以获取当前所有活跃的线程对象列表,再使用len()查看活跃的线程数量。

import time,threading
 
def sing():
    for i in range(3):
        print("is singing... %d" % i)
        time.sleep(1)
 
def dance():
    for i in range(3):
        print("is dancing... %d" % i)
        time.sleep(1)
 
 
if __name__ == '__main__':
    thread_list = threading.enumerate()
    print("\nCurrent number of threads:%d" % len(thread_list))
 
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
 
    t1.start()
    t2.start()
 
    thread_list = threading.enumerate()
    print("\nCurrent number of threads:%d" % len(thread_list))
 
 

结果:

3.线程的参数

线程传递参数有三种方法:

        1.使用元组传递:

         threading.Thread(target=xxx, args=(参数1,参数2,....))

import time,threading
 
def test(a,b,c):
    print("a=%d,b=%d,c=%d" % (a,b,c))
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=test, args=(10,20,30))
 
        # 启动子线程对象
        thread_obj.start()

结果:

        2.使用字典传递

        threading.Thread(target=xxx, kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})

import time,threading
 
def test(a,b,c):
    print("a=%d,b=%d,c=%d" % (a,b,c))
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=test, kwargs={"a":10,"c":20,"b":30,})
 
        # 启动子线程对象
        thread_obj.start()

结果:

        3.同时使用元组和字典

        threading.Thread(target=xxx, args=(参数1,参数2,....), kwargs={"参数名1":"参数值1", "参数名2":"参数值2",...})

import time,threading
 
def test(a,b,c):
    print("a=%d,b=%d,c=%d" % (a,b,c))
    time.sleep(1)
 
 
if __name__ == '__main__':
    for i in range(5):
        # 创建子线程对象
        thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
 
        # 启动子线程对象
        thread_obj.start()

结果:

4.守护线程

        如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.setDaemon(True),要在thread.start()之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。

import time,threading
 
def test(a,b,c):
    for i in range(5):
        print("正在输出:%d" % (i))
        time.sleep(1)
 
 
if __name__ == '__main__':
 
    # 创建子线程对象
    thread_obj = threading.Thread(target=test, args=(10,), kwargs={"c":20,"b":30,})
 
    # 设置线程守护:子线程守护主线程
    thread_obj.setDaemon(True)
 
    # 启动子线程对象
    thread_obj.start()
 
    time.sleep(1)
 
    print("主线程即将结束...")
 
    # 退出主线程
    exit()

结果:

5. 并行和并发

5.1 多任务的概念

        其实就是操作系统轮流让各个任务交替执行,如:任务1执行0.01秒,然后任务2执行0.01秒,再让任务3执行0.01秒...这样反复执行下去,表面上看,每个任务都是交替执行的,但是由于CPU执行速度非常快,我们就感觉所有任务都在同时执行一样。

5.2 并发和并行的概念

        并发:并发指的是任务数大于CPU核数,通过操作系统的各种调度算法,实现用多个任务“一起”执行(实际总有一些任务不执行,因为切换任务的速度相当快,看上去一起执行而已)

        并行:指的是任务数小于或等于CPU核数,即任务真的是一起执行的。

        真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行。

6. 自定义线程类

        通过threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只需要以下三步:

        1.让自定义类继承 threading.Thread

        2.让自定义类重写run方法

        3.通过实例化自定义类对象.start()方法启动自定义线程

import threading,time
 
 
# 自定义线程类
class MyThread(threading.Thread):
    def __init__(self, num):
        super().__init__()  # 要先调用父类的init方法否则会报错
        self.num = num
 
    # 重写 父类run方法
    def run(self):
        for i in range(self.num):
            print("正在执行子线程的run方法...",i)
            time.sleep(0.5)
 
if __name__ == '__main__':
    mythread = MyThread(5)
    mythread.start()

7. 多线程共享全局变量

        这里创建两个函数,在work1函数中1对全局变量g_num的值进行修改,与此同时在work2函数中获取g_num的值。

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    for i in range(10):
        g_num += 1
        time.sleep(0.5)
        print("work1------%d\n" % g_num)
 
def work2():
    for i in range(10):
        time.sleep(0.5)
        print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:(works1和works2一次输出1-10,只不过各有0.5时间间隔,总的有1s间隔,时间差出现下面这样显示结果)

8. 多线程共享全局变量的问题

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work1------%d\n" % g_num)
 
def work2():
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:

解决方法:

        1.通过join()方法,让t1线程优先执行,t1执行完毕后,t2才执行

        这种方式的缺点是把多线程变成了单线程,影响程序执行效率

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work1------%d\n" % g_num)
 
def work2():
    global g_num
 
    for i in range(1000000):
        g_num += 1
    print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t1.join()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:

         2.通过加锁解决问题

9. 同步和异步

        同步:多个任务之间执行的时候要求有先后顺序,必需一个先执行完成之后,另一个才能继续执行,只有一个主线。如:你说完,我再说(同一时间只能做一件事情)

        异步:多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在多条运行主线。如:发微信(可以不用等对方回复继续发)

10. 互斥锁

        当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

        互斥锁为资源引入一个状态:锁定/非锁定

        某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其它线程才能再次锁定该资源,互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

使用互斥锁完成两个线程对同一个全局变量各加100万次的操作:

import threading,time
 
# 定义一个全局变量
g_num = 0
 
def work1():
    # 申明g_num是一个全局变量
    global g_num
 
    lock.acquire()  # 加锁
    for i in range(1000000):
        g_num += 1
    lock.release()  # 解锁
    print("work1------%d\n" % g_num)
 
def work2():
    global g_num
 
    lock.acquire()
    for i in range(1000000):
        g_num += 1
    lock.release()
    print("work2------%d\n" % g_num)
 
if __name__ == '__main__':
    # 创建一把互斥锁
    lock = threading.Lock()
 
    t1 = threading.Thread(target=work1)
    t2 = threading.Thread(target=work2)
 
    t1.start()
    t2.start()
 
    # 在子线程结束后打印g_num
    while len(threading.enumerate()) !=1:
        time.sleep(1)
    print("main------", g_num)

结果:

11. 死锁

        在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。(比如:一条流水线负责生产螺帽,一条流水线负责生产螺母,而且两条流水线都需要负责组装,就会出现螺母等螺帽,螺帽等螺母的情况,又因为各条流水线都资源上锁,对方都拿不到,因此造成死锁)

示例:

import threading
 
 
def getValue(index):
    dataList = [1,3,5,7,9]
    lock.acquire()
 
    if index >= len(dataList):
        print("下标越界",index)
        return
    print(dataList[index])
    lock.release()
 
if __name__ == '__main__':
    # 创建锁
    lock = threading.Lock()
 
    # 创建10个线程
    for i in range(10):
        t = threading.Thread(target=getValue, args=(i,))
        t.start()

结果:(发现当下标越界后程序被阻塞了,原因是return之前没有释放锁)

解决:在return之前释放锁

import threading
 
 
def getValue(index):
    dataList = [1,3,5,7,9]
    lock.acquire()
 
    if index >= len(dataList):
        print("下标越界",index)
        lock.release()
        return
    print(dataList[index])
    lock.release()
 
if __name__ == '__main__':
    # 创建锁
    lock = threading.Lock()
 
    # 创建10个线程
    for i in range(10):
        t = threading.Thread(target=getValue, args=(i,))
        t.start()

结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/408880.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第10届蓝桥杯Scratch图形化编程 选拔赛初级组编程题1:小猫走城堡

准备工作: 导入育最库中的“Castle 3 小猫从坐标点(-165,-93)出发向城堡走去。随着位置的移动,角色大小逐渐变小,最后在城堡前消失。注意1.角色大小在逐渐变化,运行结束再次点击绿旗,程序应还能再次扶行。2.角色应该是…

云原生之API网关Traefik

1. 前言 说到web服务的开源网关,我首先想到的是Nginx,最早使用的就是它,现在都还在使用它。系统上线了Docker Swarm集群之后,不继续使用Nginx直接做Docker服务的网关,是因为Nginx毕竟比Docker Swarm出现的早&#xff0…

YOLOv7基础 | 第2种方式:简化网络结构之yolov7.yaml(由104层简化为30层)

前言:Hello大家好,我是小哥谈。通过下载YOLOv7源码可知,原始的yolov7.yaml文件是拆开写的,比较混乱,也不好理解,并且为后续改进增添了很多困难。基于此种情况,笔者就给大家介绍一种将yolov7.yaml文件简化的方法,将104层简化为30层,并且参数量和计算量和原来是一致的,…

✅鉴权—cookie、session、token、jwt、单点登录

基于 HTTP 的前端鉴权背景cookie 为什么是最方便的存储方案,有哪些操作 cookie 的方式session 方案是如何实现的,存在哪些问题token 是如何实现的,如何进行编码和防篡改?jwt 是做什么的?refresh token 的实现和意义ses…

pythonJax小记(五):python: 使用Jax深度图像(正交投影和透视投影之间的转换)(持续更新,评论区可以补充)

python: 使用Jax深度图像(正交投影和透视投影之间的转换) 前言问题描述1. 透视投影2. 正交投影 直接上代码解释1. compute_projection_parameters 函数a. 参数解释b. 函数计算 2. ortho_to_persp 函数a. 计算投影参数:b. 生成像素坐标网格&am…

protobuf简单使用(二)

介绍 上一节中,我们介绍了protobuf,简单来说,它是一种消息数据格式,其作用类似于json,但是比json的使用效率要高。 除此以外,我们介绍了protobuf的简单使用,也就是如何可以像使用json一样&…

AI游戏初创公司“奇酷网络”,获得500万元天使投资

发布 | 大力财经 2024年新春伊始的2月25日,国内首家“AI游戏”应用公司“奇酷网络”正式对外宣布:以3,000万元人民币的估值,成功获得500万元人民币的融资;资金来源于一名百度高层和一位知名的天使投资人。 据悉,“奇…

进程 2月24日学习笔记

1.进程: 程序:存放在外存中的一段数据组成的文件 进程:是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡 2.进程相关命令: 1.top 动态查看当前系统中的所有进程信息(根据CPU占用率排序) PID:唯一识…

namecheap域名如何购买?通过支付宝(详细图文教程)

引言 在完成博客搭建的时候,我们可能需要一个好的域名,自己看起来才会舒服点,同时也可以通过google或者百度等方式搜索到自己博客。 经过实验发现,一个好的后缀名会增强google和百度的搜索seo,增加自己博客的流量。 …

平头哥IP核C906的JTAG调试器DIY教程(一)

背景 最近买了一块基于平头哥C906核,SOC为全志的D1s,的核心板,手工焊接在白嫖的底板上(此处感谢百问网老师的友情支持,手动狗头)。在焊接完成后,进行点亮跑程序的时候,发现没有优雅…

C++ //练习 8.13 重写本节的电话号码程序,从一个命名文件而非cin读取数据。

C Primer(第5版) 练习 8.13 练习 8.13 重写本节的电话号码程序,从一个命名文件而非cin读取数据。 环境:Linux Ubuntu(云服务器) 工具:vim 代码块 /***************************************…

关于设备连接有人云的使用及modbus rtu协议,服务器端TCP调试设置

有人云调试 调试过程问题1. 关于modbus rtu协议,实质上有三种modbus基本原理modbus 格式2. 关于modbus crc16通信校验3. 关于在ubuntu阿里云服务器端,监听网络数据之调试mNetAssist4. 使用有人FAE传给的设置软件问题???之前的一个项目,再拿出来回顾下。 调试过程 先 要在有…

web安全学习笔记【16】——信息打点(6)

信息打点-语言框架&开发组件&FastJson&Shiro&Log4j&SpringBoot等[1] #知识点: 1、业务资产-应用类型分类 2、Web单域名获取-接口查询 3、Web子域名获取-解析枚举 4、Web架构资产-平台指纹识别 ------------------------------------ 1、开源-C…

特征选择|一种提升预测模型性能的方法(原理及其优化实现,Matlab)

文章来源于我的个人公众号:KAU的云实验台,主要更新智能优化算法的原理、应用、改进 如今,生成的数据集呈指数级增长,这将产生具有大量特征和样本的数据集,而显然,某些特征是不相关/冗余的,它们…

奇异递归模板模式应用6-类模板enable_shared_from_this

异步编程中存在一种场景,需要在类中将该类的对象注册到某个回调类或函数中,不能简单地将this传递给回调类中,很可能因为回调时该对象不存在而导致野指针访问(也有可能在析构函数解注册时被回调,造成对象不完整&#xf…

【C语言基础】:操作符详解(一)

文章目录 操作符详解1. 操作符的分类2. 二进制和进制转换2.1 什么是二进制、八进制、十进制、十六进制2.1.1 二进制和进制转换2.1.2 二进制转十进制2.2.3 二进制转八进制2.2.4 二进制转十六进制 3. 源码、反码、补码4. 移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&…

IT廉连看——C语言——函数

IT廉连看——C语言——函数 一、函数是什么? 数学中我们常见到函数的概念。但是你了解C语言中的函数吗? 维基百科中对函数的定义:子程序 在计算机科学中,子程序(英语:Subroutine, procedure, function, …

【Java】java异常处理机制(实验五)

目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 1、理解java的异常处理机制 2、掌握try catch结构和thow和thows关键字的用法 二、实验内容 1、编写一个程序,输入某个班某门课程成绩,统计及格人数、不及格人数及课程平均分。设计一个异…

H12-821_59

59.R1、R2、R3、R4运行IS-IS,它们接口的DIS Priority如图所示,假如设备同时启动,则()被选举为D1S.(请填写设备名称、例如R1) 答案:R4 注释: IS-IS中DIS的选举支持抢占。 假设题目说R4最后启动,问谁被选举为DIS,答案仍然是R4。

【嵌入式实践】【芝麻】【设计篇-1】从0到1给电动车添加指纹锁:项目设计思路

0. 前言 该项目是基于stm32F103和指纹模块做了一个通过指纹锁控制电动车的小工具。支持添加指纹、删除指纹,电动车进入P档等待时计时,计时超过5min则自动锁车,计时过程中按刹车可中断P档状态,同时中断锁车计时。改项目我称之为“芝…