python实操马上毕业了,你还不懂什么是守护线程线程进程?(附12306抢票程序-源代码)

Posted 我爱吃必胜客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python实操马上毕业了,你还不懂什么是守护线程线程进程?(附12306抢票程序-源代码)相关的知识,希望对你有一定的参考价值。

  • 作者:20岁爱吃必胜客(坤制作人),近十年开发经验, 跨域学习者,目前于海外某世界知名高校就读计算机相关专业。
  • 荣誉:阿里云博客专家认证、腾讯开发者社区优质创作者,在CTF省赛校赛多次取得好成绩。
  • 跨领域学习,喜欢摄影、弹吉他、咏春拳。文章深入浅出、语言风趣;爱吃必胜客社区创立者,旨在“发现美 欣赏美


文章目录

⭐️前言

下面我们先回顾基础知识,分别是:

进程-process
线程-Treading
守护线程-Deamon Treading

🌟 进程-process

进程就是一个装线程的容器
是线程的容器

进程就是一个个正在运行的独立软件

🌟 线程-Treading

又叫Light Weight Process LWP
轻量级进程
程序执行流的最小单元

线程组成:

线程id-----当前指令指针------寄存器集合---------堆栈

🌟 守护线程-Deamon Treading

☀️java中的应用

守护线程(Daemon Thread)是一种特殊的线程,其生命周期与 Java 虚拟机(JVM)的生命周期相同。当 JVM 中已不存在任何非守护线程时,虚拟机会自动退出,守护线程也会随之结束。因此,守护线程也被称为“服务线程”或“后台线程”

守护线程主要用于执行一些低优先级的任务,比如垃圾回收、内存管理、日志维护等工作。它通常不干扰其他线程的执行,当所有非守护线程执行结束后,它会被自动中断。

在 Java 中创建守护线程的方法是通过 Thread 类的 setDaemon() 方法,将线程设置为守护线程。当线程启动后,也可以使用 isDaemon() 方法来检查该线程是否为守护线程

需要注意的是,守护线程和非守护线程的区别在于它们的执行权限守护线程不能访问程序中的非守护线程或共享资源。因此,在使用守护线程时需要仔细考虑线程之间的依赖关系和共享资源的使用。

☀️python中的应用

Python中也有守护线程的概念,它与Java中的作用是相似的。在Python中,可以通过Thread类中的setDaemon方法来将线程设置为守护线程

在Python中,守护线程通常用于执行一些低优先级的任务或后台服务,例如监控另一个线程是否终止、自动保存数据等。当所有非守护线程结束时,守护线程也会自动结束,不会阻塞主进程的结束,这在一些长时间运行的程序中非常有用

需要注意的是,守护线程并不是万能的解决方案,它不能处理复杂的计算任务和涉及共享资源的并发问题。此外,在使用守护线程时需要仔细考虑线程之间的依赖关系和共享资源的使用,以避免数据竞争和死锁等问题。

下面是一个简单的例子,展示了如何在Python中创建守护线程:

import threading
import time

def print_time():
    for i in range(5):
        print("Current time:", time.ctime())
        time.sleep(1)

t = threading.Thread(target=print_time)
t.daemon = True # 将线程设置为守护线程
t.start()

print("Main process end.")

在上面的代码中,创建了一个名为print_time的函数,通过time模块打印当前时间,并在每次打印后等待1秒。然后创建了一个线程对象t,将print_time函数作为其目标函数,然后将线程设置为守护线程并启动。最后主进程打印一条结束信息。

⭐️多线程模块threading

现在用threading取代了thread模块。
Python中的threading模块提供了一种方便的方式来创建和管理线程。下面是使用threading模块创建和管理线程的示例代码:

import threading

# 定义一个线程执行的任务函数
def task():
    print("This is a task function.")
    
# 创建一个新线程
t = threading.Thread(target=task)

# 启动该线程
t.start()

# 等待该线程结束
t.join()

print("Main thread ends.")

在上面的代码中,首先定义了一个task()函数,用于表示线程执行的具体任务。然后通过threading.Thread()创建一个新的线程对象t,并将task函数作为其目标函数。最后启动该线程并等待其运行结束,然后主线程继续执行。

除了这个基本的线程创建和启动方式,threading模块还提供了一些方便的功能,例如:

  • threading.current_thread():返回当前线程对象;
  • threading.active_count():返回当前活跃的线程数;
  • threading.enumerate():返回当前所有活跃线程的列表;
  • threading.Lock():创建一个锁对象,用于保护共享资源的互斥操作;
  • threading.Event():创建一个事件对象,用于线程之间的通信和同步等。

🌟 使用锁来保护共享资源的访问

下面是一个例子,演示如何使用锁来保护共享资源的访问

import threading

# 定义一个共享变量
num = 0

# 创建一个锁对象
lock = threading.Lock()

# 定义一个线程执行的任务函数
def task():
    global num
    for i in range(1000000):
        lock.acquire()
        num += 1
        lock.release()

# 创建多个线程
threads = [threading.Thread(target=task) for i in range(10)]

# 启动这些线程
for t in threads:
    t.start()

# 等待这些线程结束
for t in threads:
    t.join()

print("Final result:", num)

在上面的代码中,首先定义了一个共享变量num,它的值将被多个线程共同更新。然后创建一个锁对象lock,用于保护num变量的访问。接下来定义一个task()函数。

⭐️queue模块

实现多生产者,多消费者队列
该技术是多线程安全共享数据的最佳选择技术之一。

⭐️多线程购买火车票的代码

以下是一个多线程购买火车票的代码示例:

import threading
import time

class Ticket:
    def __init__(self, name, quantity):
        self.name = name
        self.quantity = quantity
        self.lock = threading.Lock()
        
    def buy(self, num):
        self.lock.acquire()
        try:
            if self.quantity >= num:
                self.quantity -= num
                print(f"当前剩余车票:self.quantity")
                return True
            else:
                print("余票不足,购票失败")
                return False
        finally:
            self.lock.release()

def worker(name, ticket, num):
    while True:
        if ticket.buy(num):
            print(f"name 购买 num 张车票成功!")
            break
        else:
            print(f"name 正在尝试购买车票中...")
            time.sleep(1)

if __name__ == '__main__':
    t = Ticket("北京-上海", 10)
    threads = []
    for i in range(5):
        name = f"用户i+1"
        num = 2
        t1 = threading.Thread(target=worker, args=(name, t, num))
        threads.append(t1)
    for t in threads:
        t.start()
    for t in threads:
        t.join()

代码说明:

  1. Ticket 类表示火车票,包括票务名称和余票数量,以及一个锁对象用于线程同步。
  2. buy 方法用于购买车票,传入要购买的数量,如果余票足够则减少对应数量的余票,并返回 True,否则返回 False。
  3. worker 函数表示一个购票线程,不断尝试购买车票,如果成功则输出购买成功的信息,退出循环;否则等待 1 秒后重试。
  4. 在程序运行时创建 5 个购票线程,每个线程购买 2 张车票。线程启动后并发执行购票任务,直到所有线程购买成功为止。

注意:在实际开发中,购买车票需要连接网络、进行 IO 操作,并且可能会遇到一些异常情况,因此需要添加异常处理等相关代码来保证程序的稳定性和健壮性。

⭐️12306抢票程序-源代码

抢火车票需要模拟登陆、查询余票、提交订单等步骤,比较复杂,建议使用官方提供的API。以下是一个简单的模拟查询余票的Python代码示例。

import urllib.request
import json
import time

# 设置参数
fromStation = '北京'
toStation = '上海'
departureDate = '2022-01-01'

# 利用API查询余票
url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=&leftTicketDTO.from_station=&leftTicketDTO.to_station=&purpose_codes=ADULT'.format(departureDate, fromStation, toStation)
request = urllib.request.Request(url)
# 添加请求头
request.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/58.0.3029.110 Safari/537.3')
response = urllib.request.urlopen(request)
result = response.read().decode('utf-8')
data = json.loads(result)

# 解析结果
trains = data['data']['result']
for train in trains:
    trainList = train.split('|')
    if trainList[0] == '':  # 过滤无效车次
        continue
    if trainList[29] == 'Y':  # 判断是否有余票
        print('车次:,余票数量:'.format(trainList[3], trainList[29]))

# 设置查询间隔
time.sleep(10)

需要注意的是,12306官方接口的调用频率有限制,需要合理控制查询间隔,防止被封IP。此外,需要获取验证码和自动提交订单等功能可以使用第三方库,比如PyAutoGUI。但是使用自动化脚本抢票有一定风险,建议谨慎操作。

你还不懂JVM垃圾回收及收集器么?

Serial收集器

  • 这个收集器是一个单线程工作的收集器,但它的“ 单线 程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,
  • 更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

ParNew收集器

  • ParNew收集器实质上是Serial收集器的多线程并行版本

==收集器中的并发与并行==

  • 并行(Parallel):并行􏰀述的是多条垃圾收集器线程之间的关系
  • 说明同一时间有多条这样的线 程在协同工作,通常默认此时用户线程是处于等待状态。
  • 并发(Concurrent):并发􏰀述的是垃圾收集器线程与用户线程之间的关系
  • 说明同一时间垃圾 收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于 垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

Parallel Scavenge收集器

  • 基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器
  • 供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数
  • 以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

Serial Old收集器

  • Serial Old是Serial收集器的老年代版本,
  • 它同样是一个单线程收集器,使用标记-整理算法

Parallel Old收集器

  • Parallel Old是Parallel Scavenge收集器的老年代版本
  • 支持多线程并发收集,基于标记-整理算法实现。

CMS

  • 以获取最短回收停顿时间为目标的收集器

==四个步骤==

  1. 初始标记(CMS initial mark)
  2. 并发标记(CM S concurrent mark)
  3. 重新标记(CM S remark)
  4. 并发清除(CMS concurrent sweep)

==优点==

  • 并发收集、低停顿

==缺点==

  • 它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低总吞吐量
  • 在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,
  • 程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,
  • CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集 时再清理掉。这一部分垃圾就称为“ 浮动垃圾”

Garbage First收集器

  • 遵循分代收集,开创的基于Region的堆内存布局是它能够实现这个目标的关键
  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要 停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际 并没有额外的停顿。
  • 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫􏰁整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫􏰁完成以后 , 还要重新处理SATB记录下的在并发是引用的并发对象
  • 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下 来 的 最 后 那 少 量 的SATB记录 。
  • 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。

 最后

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。

现在竞争这么激烈,只有通过不断学习,提高自己,才能保持竞争力。

对于一些不知道学习什么,没有一个系统路线的程序员,这里给大家提供一些学习资料

需要的小伙伴,可以一键三连,点击这里获取免费领取方式

《Java核心知识点合集(283页)》

内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等

《Java中高级核心知识点合集(524页)》

《Java高级架构知识点整理》

《Docker从入门到实践》

《spring could 学习笔记》

《JVM与性能调优知识点整理》

《MySQL性能调优与架构设计解析文档》305页

《Nginx入门到实战》319页

《Java并发编程》385页

《1000道 互联网Java工程师面试题 (485页)》

需要的小伙伴,可以一键三连,点击这里获取免费领取方式 

以上是关于python实操马上毕业了,你还不懂什么是守护线程线程进程?(附12306抢票程序-源代码)的主要内容,如果未能解决你的问题,请参考以下文章

全民响应式编程,你还不懂RxJava吗

你还不懂云计算吗?

为了追学姐,用python把她的照片做成了游戏,她看了...

深入拆解类加载器,这样的姿势你还不懂吗?

动态规划不信看完你还不懂动态规划

动态规划不信看完你还不懂动态规划