【Python基础】进程

news2024/11/16 13:42:58

文章目录

    • @[toc]
      • 程序与进程的区别与联系
      • 同步任务
        • 示例
      • 并行任务
        • 示例
        • 进程调度的“随机性”
      • 进程属性与方法
        • process_object.start()方法
        • process_object.join()方法
        • process_object.daemon属性
          • 没有设置守护进程的情况
          • 设置守护进程的情况
        • process_object.current_process()方法
      • 进程通信
        • 队列的使用
          • put()方法与full()方法
          • get()方法与empty()方法
          • put_nowait()方法
          • get_nowait()方法
          • task_done()方法与join()方法
            • 队列内置计数器的值不为0
            • 队列内置计数器的值为0
        • 使用队列完成进程通信
      • 进程池
        • multiprocessing.Pool
        • ProcessPoolExecutor API
          • 进程池的创建
          • done()方法
          • cancel()方法
          • result()方法
          • as_completed()方法
          • map()方法
          • wait()方法
      • 自定义进程类
        • 示例

因上努力

个人主页:丷从心·

系列专栏:Python基础

学习指南:Python学习指南

果上随缘


程序与进程的区别与联系

  • 程序是指存储在磁盘或其他存储介质上的一组指令或代码,是静态文件
  • 进程是程序的执行实例,是一个动态的实体,具有独立的内存空间和系统资源

同步任务

  • 我们在此之前编写的代码都是同步代码,代码从上到下按顺序执行,如果前一个任务没有完成,那么不能运行之后的任务
示例
import time


def work_1():
    print('任务1...')

    time.sleep(2)


def work_2():
    print('任务2...')

    time.sleep(2)


start = time.time()

work_1()
work_2()

end = time.time()

print(f'总共用时: {end - start} s')
任务1...
任务2...
总共用时: 4.020323038101196 s
  • 可以看到整个程序用时 4 4 4秒,work_2()需要等待work_1()运行结束后才能运行

并行任务

  • 使用进程来运行上面的代码,能够优化运行时间
示例
import time
import multiprocessing


def work_1():
    print('任务1...')

    time.sleep(2)


def work_2():
    print('任务2...')

    time.sleep(2)


# Windows 操作系统下进程任务必须拥有 main 入口
if __name__ == '__main__':
    # 通过 Process 类创建进程对象, 并使用 target 绑定进程对象要运行的任务
    p1 = multiprocessing.Process(target=work_1)
    p2 = multiprocessing.Process(target=work_2)

    start = time.time()

    # 运行进程
    p1.start()
    p2.start()

    p1.join()
    p2.join()

    end = time.time()

    print(f'总共用时: {end - start} s')
任务1...
任务2...
总共用时: 2.6914994716644287 s
  • 可以看到整个程序用时约 3 3 3秒,work_1()work_2并行运行
进程调度的“随机性”
  • 下面的示例可以看到操作系统调度进程时的“随机性”
import time
import threading


def work_1():
    for i in range(5):
        print('任务1...')

        time.sleep(2)


def work_2():
    for i in range(5):
        print('任务2...')

        time.sleep(2)


t1 = threading.Thread(target=work_1)
t2 = threading.Thread(target=work_2)

t1.start()
t2.start()
任务2...
任务1...
任务1...
任务2...
任务2...
任务1...
任务1...
任务2...
任务2...
任务1...
  • 可以看到任务 1 1 1和任务 2 2 2的调度顺序是我们无法确定的,是由操作系统的调度算法决定的

进程属性与方法

  • 在学习进程方法之前,我们需要知道Python程序是如何被运行的
    • 一个Python文件被解释器运行时会在操作系统中创建一个进程
    • 然后该进程会创建一个线程来运行文件中的代码,这个程序最初创建的线程称为主线程
    • 当主线程运行到p = multiprocessing.Process()时会创建一个新的进程,称为子进程
    • 主进程与子进程由操作系统进行调度,并发地运行,具体如何调度进程由操作系统的调度算法决定
    • 子进程在运行时,主进程不会等待子进程,而是继续向下执行,直到执行到文件末尾没有代码时,主进程会等待子进程运行结束后再退出
process_object.start()方法
  • p = multiprocessing.Process()只是创建了一个进程,并不会运行进程代码

  • p.start()使进程p达到就绪状态,等待操作系统进行调度,具体何时调度由操作系统决定

  • 以上面的并行任务的代码为例,先注释掉p1.join()p2.join()

import time
import multiprocessing


def work_1():
    print('任务1...')

    time.sleep(2)


def work_2():
    print('任务2...')

    time.sleep(2)


# Windows 操作系统下进程任务必须拥有 main 入口
if __name__ == '__main__':
    # 通过 Process 类创建进程对象, 并使用 target 绑定进程对象要运行的任务
    p1 = multiprocessing.Process(target=work_1)
    p2 = multiprocessing.Process(target=work_2)

    start = time.time()

    # 运行进程
    p1.start()
    p2.start()

    # p1.join()
    # p2.join()

    end = time.time()

    print(f'总共用时: {end - start} s')
总共用时: 0.012919902801513672 s
任务1...
任务2...
  • 可以看到主进程没有等待子进程,而是继续向下执行
  • 当执行到end = time.time()时,此时end记录的时间是主进程运行到这行代码的时间
  • 之后运行print(f'总共用时: {end - start} s'),输出时间0.012919902801513672 s,此时执行到了文件末尾没有其他代码,主进程会等待子进程运行结束后再退出
  • 为了能正确记录进程运行的时间,我们需要让主进程等待子进程
process_object.join()方法
  • p.join()使主进程等待子进程,子进程任务执行结束后主进程再继续向下执行
  • 仍然以上面的并行任务的代码为例,取消注释p1.join()p2.join()
import time
import multiprocessing


def work_1():
    print('任务1...')

    time.sleep(2)


def work_2():
    print('任务2...')

    time.sleep(2)


# Windows 操作系统下进程任务必须拥有 main 入口
if __name__ == '__main__':
    # 通过 Process 类创建进程对象, 并使用 target 绑定进程对象要运行的任务
    p1 = multiprocessing.Process(target=work_1)
    p2 = multiprocessing.Process(target=work_2)

    start = time.time()

    # 运行进程
    p1.start()
    p2.start()

    p1.join()
    p2.join()

    end = time.time()

    print(f'总共用时: {end - start} s')
任务1...
任务2...
总共用时: 2.6913790702819824 s
  • 可以看到主进程等待子进程运行结束后才继续向下执行,正确记录了子进程运行的时间
process_object.daemon属性
  • 设置守护进程,需要在进程启动之前进行设置
  • 如果一个进程是守护进程,那么主进程运行到文件末尾后不论子进程任务是否结束都会自动退出
没有设置守护进程的情况
import time
import multiprocessing


def work():
    for i in range(5):
        print(i)

        time.sleep(1)


if __name__ == '__main__':
    p = multiprocessing.Process(target=work)
    # p.daemon = True

    p.start()

    print('主进程即将退出...')
主进程即将退出...
0
1
2
3
4
设置守护进程的情况
import time
import multiprocessing


def work():
    for i in range(5):
        print(i)

        time.sleep(1)


if __name__ == '__main__':
    p = multiprocessing.Process(target=work)
    p.daemon = True

    p.start()

    print('主进程即将退出...')
主进程即将退出...
  • 可以看到并没有输出 0 0 0 1 1 1 2 2 2 3 3 3 4 4 4,主进程就退出了
process_object.current_process()方法
  • process_object.current_process()方法用于获取当前进程对象的引用
  • 可以用来获取进程名称和进程号
import multiprocessing
import os


def work():
    pid = multiprocessing.current_process().pid
    ppid = os.getppid()
    name = multiprocessing.current_process().name

    print(f'pid: {pid},\t ppid: {ppid}, name: {name}')


if __name__ == '__main__':
    for i in range(5):
        t = multiprocessing.Process(target=work)
        t.name = f'进程-{i}'

        t.start()
        t.join()
pid: 16464,	 ppid: 23240, name: 进程-0
pid: 21140,	 ppid: 23240, name: 进程-1
pid: 7884,	 ppid: 23240, name: 进程-2
pid: 19412,	 ppid: 23240, name: 进程-3
pid: 6196,	 ppid: 23240, name: 进程-4
  • os.getppid()方法用于获取进程的父进程号

进程通信

队列的使用
put()方法与full()方法
  • put()方法用于将数据上传到队列
  • full()方法用于判断队列是否已满
from queue import Queue

queue = Queue(3)  # 设置队列最大容量为 3

queue.put(1)
queue.put(2)
queue.put(3)

print(queue.full())

queue.put(4)  # 因为队列已满, 所以会导致阻塞, 直到队列重新有空闲空间时解阻塞
True
  • 当队列中已满时,使用put()方法会阻塞,直到队列重新重新有空闲空间时解阻塞
get()方法与empty()方法
  • get()方法用于从队列中读取数据
  • empty()方法用于判断队列是否为空
from queue import Queue

queue = Queue()

queue.put(1)
queue.put(2)
queue.put(3)

print(queue.get())
print(queue.get())
print(queue.get())
print(queue.get())  # 因为队列为空, 所以会导致阻塞, 直到队列重新有数据时解阻塞
1
2
3
  • 当队列中为空时,使用get()方法会阻塞,直到队列重新有数据时解阻塞
put_nowait()方法
  • put_nowait()方法用于向队列中上传数据,如果队列已满,则抛出异常
from queue import Queue

queue = Queue(3)  # 设置队列最大容量为 3

queue.put(1)
queue.put(2)
queue.put(3)

queue.put_nowait(4)  # 如果队列已满, 则抛出异常
Traceback (most recent call last):
  File "C:/Users/FOLLOW_MY_HEART/Desktop/Python Basics/【Python基础】进程/test.py", line 9, in <module>
    queue.put_nowait(4)  # 如果队列已满, 则抛出异常
  File "D:\Environment\anaconda\envs\py310\lib\queue.py", line 191, in put_nowait
    return self.put(item, block=False)
  File "D:\Environment\anaconda\envs\py310\lib\queue.py", line 137, in put
    raise Full
queue.Full
get_nowait()方法
  • get_nowait()方法用于从队列中读取数据,如果队列为空,则抛出异常
from queue import Queue

queue = Queue()

queue.put(1)
queue.put(2)
queue.put(3)

print(queue.get())
print(queue.get())
print(queue.get())
print(queue.get_nowait())  # 如果队列为空, 则抛出异常
1
2
3
Traceback (most recent call last):
  File "C:/Users/FOLLOW_MY_HEART/Desktop/Python Basics/【Python基础】进程/test.py", line 12, in <module>
    print(queue.get_nowait())  # 如果队列为空, 则抛出异常
  File "D:\Environment\anaconda\envs\py310\lib\queue.py", line 199, in get_nowait
    return self.get(block=False)
  File "D:\Environment\anaconda\envs\py310\lib\queue.py", line 168, in get
    raise Empty
_queue.Empty
task_done()方法与join()方法
  • queue.get()会使队列内置计数器的值加 1 1 1,而queue.get()不会使队列内置计数器的值减 1 1 1

  • task_done()方法用于将队列内置计数器的值减 1 1 1

  • join()方法用于判断队列内置计数器的值是否为 0 0 0,如果不为 0 0 0则会阻塞,直到队列内置计数器的值为 0 0 0时解阻塞

队列内置计数器的值不为0
from queue import Queue

queue = Queue()
queue.put(1)

queue.get()

queue.join()

print('不能运行到这里...')

队列内置计数器的值为0
from queue import Queue

queue = Queue()
queue.put(1)

queue.get()
queue.task_done()

queue.join()

print('能够运行到这里...')
能够运行到这里...
使用队列完成进程通信
from multiprocessing import Process, Queue, Event


# 向队列中写入数据
def write(queue, event):
    for num in range(10):
        queue.put(num)

        event.set()

        print('正在上传...')

    queue.put('END')
    event.set()


# 从队列中读取数据
def read(queue, event):
    while True:
        event.wait()

        num = queue.get()
        if num == 'END':
            break

        print(f'从队列中读取到的数据为: {num}')


if __name__ == '__main__':
    # 创建队列对象
    queue = Queue()
    event = Event()

    p_write = Process(target=write, args=(queue, event))
    p_read = Process(target=read, args=(queue, event))

    p_write.start()
    p_read.start()

    p_write.join()
    p_read.join()

    print('任务完成...')
正在上传...
从队列中读取到的数据为: 0
正在上传...
从队列中读取到的数据为: 1
正在上传...
正在上传...
正在上传...
从队列中读取到的数据为: 2
正在上传...
从队列中读取到的数据为: 3
从队列中读取到的数据为: 4
从队列中读取到的数据为: 5
正在上传...
正在上传...
正在上传...
从队列中读取到的数据为: 6
从队列中读取到的数据为: 7
从队列中读取到的数据为: 8
正在上传...
从队列中读取到的数据为: 9
任务完成...

进程池

  • 进程对象的创建需要时间,在需要创建大量进程对象的时候会发生性能下降的情况
  • 使用进程池会创建出一定数量的进程对象,并且进程在执行完任务后不会被操作系统销毁,这样下一个任务可以重复使用之前创建的这些进程对象
multiprocessing.Pool
import time
import os
from multiprocessing import Pool


def get_html(page):
    time.sleep(1)

    pid = os.getpid()
    print(f'pid: {pid},\t Successfully obtained page {page}...')

    return page


if __name__ == '__main__':
    # 创建进程池对象
    pool = Pool(3)

    # 通过 apply_async 提交需要异步执行的函数到进程池中
    for i in range(10):
        pool.apply_async(get_html, args=(i,))

    pool.close()  # 关闭进程池提交
    pool.join()
pid: 20516,	 Successfully obtained page 1...
pid: 23224,	 Successfully obtained page 0...
pid: 15036,	 Successfully obtained page 2...
pid: 23224,	 Successfully obtained page 4...
pid: 20516,	 Successfully obtained page 3...
pid: 15036,	 Successfully obtained page 5...
pid: 20516,	 Successfully obtained page 7...
pid: 23224,	 Successfully obtained page 6...
pid: 15036,	 Successfully obtained page 8...
pid: 20516,	 Successfully obtained page 9...
ProcessPoolExecutor API
进程池的创建
import time
from concurrent.futures import ProcessPoolExecutor


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=2)

    # 通过 submit 提交需要执行的函数到进程池中
    task_1 = executor.submit(get_html, 1)
    task_2 = executor.submit(get_html, 2)
Successfully obtained page 1...
Successfully obtained page 2...
done()方法
  • done()方法用于判断某个任务是否完成
import time
from concurrent.futures import ProcessPoolExecutor


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=2)

    # 通过 submit 提交需要执行的函数到进程池中
    task_1 = executor.submit(get_html, 1)
    task_2 = executor.submit(get_html, 2)

    # done() 方法用于判断某个任务是否完成
    print(f'task_1 完成情况: {task_1.done()}')
task_1 完成情况: False
Successfully obtained page 1...
Successfully obtained page 2...
cancel()方法
  • cancel()方法用于取消未运行的任务,已经运行的任务无法被取消
import time
from concurrent.futures import ProcessPoolExecutor


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=1)

    # 通过 submit 提交需要执行的函数到进程池中
    task_1 = executor.submit(get_html, 1)
    task_2 = executor.submit(get_html, 2)

    # cancel() 方法用于取消未运行的任务, 已经运行的任务无法被取消
    print(f'task_2 任务取消: {task_2.cancel()}')
task_2 任务取消: True
Successfully obtained page 1...
  • 通过将max_workers的值修改为 1 1 1,使得task_2未能运行时就被取消
result()方法
  • submit()方法的返回值是一个future对象
  • 通过对future对象调用result()方法可以获取任务的返回值
import time
from concurrent.futures import ProcessPoolExecutor


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=2)

    # 通过 submit 提交需要执行的函数到进程池中
    task_1 = executor.submit(get_html, 1)
    task_2 = executor.submit(get_html, 2)

    # 通过对 future 对象调用 result() 方法获取任务的返回值
    print(f'task_1 返回结果: {task_1.result()}')
    print(f'task_2 返回结果: {task_2.result()}')
Successfully obtained page 1...
Successfully obtained page 2...
task_1 返回结果: 1
task_2 返回结果: 2
as_completed()方法
  • as_completed()方法用于获取已经执行成功的任务的返回值
import time
from concurrent.futures import ProcessPoolExecutor, as_completed


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=2)

    page_list = [1, 2, 3, 4]

    # 批量提交任务并获取已经执行成功的任务的返回值
    all_tasks = [executor.submit(get_html, page) for page in page_list]

    # 只要进程任务执行完就能获取到返回值, 完成一个任务获取一个任务的返回值
    for future in as_completed(all_tasks):
        data = future.result()

        print(f'Get data {data}')
Successfully obtained page 1...
Successfully obtained page 2...
Successfully obtained page 3...
Get data 1
Successfully obtained page 4...
Get data 2
Get data 3
Get data 4
map()方法
  • map()方法用于提交任务并获取已经执行成功的任务的返回值
import time
from concurrent.futures import ProcessPoolExecutor


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=2)

    page_list = [1, 2, 3, 4]

    # map() 方法用于提交任务并获取已经执行成功的任务的返回值
    for data in executor.map(get_html, page_list):
        print(f'Get data {data}')  # 打印的返回值顺序与列表顺序一致
Successfully obtained page 1...
Successfully obtained page 2...
Successfully obtained page 3...
Successfully obtained page 4...
Get data 1
Get data 2
Get data 3
Get data 4
wait()方法
  • wait()方法用于使主进程堵塞,直到指定任务完成后,主进程才解堵塞
import time
from concurrent.futures import ProcessPoolExecutor, wait


def get_html(page):
    print(f'Successfully obtained page {page}...')

    time.sleep(1)

    return page


if __name__ == '__main__':
    # 创建进程池对象
    executor = ProcessPoolExecutor(max_workers=2)

    page_list = [1, 2, 3, 4]

    # 批量提交任务并获取已经执行成功的任务的返回值
    all_tasks = [executor.submit(get_html, page) for page in page_list]

    # wait() 方法用于使主进程堵塞, 直到指定任务完成后, 主进程才解堵塞
    wait(all_tasks)

    print('主进程解堵塞, 执行剩余代码...')
Successfully obtained page 1...
Successfully obtained page 2...
Successfully obtained page 3...
Successfully obtained page 4...
主进程解堵塞, 执行剩余代码...

自定义进程类

  • 自定义进程类需要继承Process
  • 需要重写Process类中的run()方法,用于运行进程任务
示例
import requests
import multiprocessing


class ProcessSpider(multiprocessing.Process):
    def __init__(self, url):
        super().__init__()

        self.url = url

    def run(self):
        response = requests.get(self.url).content

        file_name = self.url.split('/')[-1]
        with open(file_name, 'wb') as f:
            f.write(response)

        print('下载完成...')


if __name__ == '__main__':
    url_list = [
        'http://pic.bizhi360.com/bbpic/98/10798.jpg',
        'http://pic.bizhi360.com/bbpic/92/10792.jpg',
        'http://pic.bizhi360.com/bbpic/86/10386.jpg'
    ]

    for url in url_list:
        process_spider = ProcessSpider(url)

        process_spider.start()
下载完成...
下载完成...
下载完成...

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySql主从复制(多主多从)

多主多从 序言前期准备工作创建主机和从机的配置文件和数据存储目录配置master1配置文件配置master2配置文件配置slave1配置文件配置slave2配置文件docker-compose-mysql.yml启动文件启动master1启动master2两个主机分别创建用户两个主机分别刷新权限查看两个主机binlog状态mas…

UDP报头结构 和注意事项

UDP协议 UDP这个协议的学习 ,最只要学习的就是报文格式 UDP数据报 UDP报头 UDP载荷 UDP协议端口格式 报头由4个部分组成 : 源端口号 、目的端口号、UDP长度、UDP校验和 UDP长度描述了整个UDP数据报&#xff0c;占多少个字节 通过UDP长度 就可以知道&#xff0c;当前载荷一…

第3章 内存管理(2)

3.2虚拟内存管理 3.2.1 传统内存和虚拟内存对比 传统内存虚拟内存一次性:全部装入内存才能运行多次性:仅装入当前需要的数据驻留性:作业装入内存,运行时一直驻留内存对换性:暂时用不到的数据换出外存虚拟性:逻辑上扩充内存作业过大无法装入,作业过多也无法装入需要添加请求调…

3.10设计模式——Template Method 模版方法模式(行为型)

意图 定义一个操作中的算法骨架&#xff0c;而将一些步骤延迟到子类中&#xff0c;Template Method 使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。 结构 AbstractClass&#xff08;抽象类&#xff09;定义抽象的原语操作&#xff0c;具体的子类将重定…

贪吃蛇(下)游戏的实现

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 文章目录 前言一.蛇和食物的打印二.游戏的运行逻辑三.结束游戏 &#xff08;善后工作&#xff09;四.游戏的测…

MacBook Pro 原生安装 Ubuntu 24.04 ARM 版

趁着休假整理家里闲置的设备&#xff0c;看到了一台许久不用的 M2 芯片的 MacBook Pro&#xff0c;想着或许应该把它改造成 ARMv64 的 CI/CD 构建机&#xff0c;于是就有了这篇文章。 本篇文章适用于 M1、M2 全系列的设备&#xff0c;包括&#xff1a;MacBook Air、MacBook Pr…

JVM笔记2--垃圾收集算法

1、如何确认哪些对象“已死” 在上一篇文章中介绍到Java内存运行时的各个区域。其中程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生&#xff0c;随线程而灭&#xff0c;栈中的栈帧随着方法的进入和退出而有条不紊的执行着入栈和出栈操作。每个栈帧中分配多少内存基本上…

OpenCV多张图片堆叠显示

OpenCV实现多张图片堆叠显示 程序思路效果代码 程序思路 读取两张或多张图片&#xff1b;获取图片尺寸&#xff1b;选择多张图片中较大的宽度和高度建立画布&#xff1b;合并图片到画布&#xff1b; 效果 代码 import cv2 import numpy as np# 读取两张图片 img1 cv2.imrea…

【软件开发规范篇】JAVA后端开发编程规范

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

Endnote X9 20 21如何把中文引用的et al 换(变)成 等

描述 随着毕业的临近&#xff0c;我在写论文时可能会遇到在引用的中文参考文献中出现“et al”字样。有的学校事比较多&#xff0c;非让改成等等&#xff0c;这就麻烦了。 本身人家endnote都是老美的软件&#xff0c;人家本身就是针对英文文献&#xff0c;你现在让改成等等&a…

Fetch的概述和基本使用

03 【Fetch的概述和基本使用】 1.XMLHttpRequest缺点 浏览器提供了原生的AJAX实现类XMLHttpRequest&#xff0c;基于该类实例&#xff0c;我们可以实现在网页上发送AJAX请求到服务端。 但是XMLHttpRequest的设计并不完美&#xff0c;主要体现在以下几个方面&#xff1a; HT…

贪吃蛇(上)Win32API

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 文章目录 前言一、Win32 API二、地图的绘制和初始化总结 前言 贪吃蛇&#xff08;也叫做贪食蛇&#xff09;游…

Noir Dark Mode for Safari:夜间浏览的舒适伴侣

Noir Dark Mode for Safari是一款实用的浏览器插件&#xff0c;它使夜间浏览网页变得更加轻松和舒适。通过自动为访问的每个网站添加暗色模式&#xff0c;Noir减少了用户在暗光环境下浏览网页时可能产生的眼睛疲劳。 Noir的自定义功能允许用户根据自己的喜好调整暗色模式的设置…

配电室智能巡检机器人

近年来&#xff0c;生产过程高度自动化&#xff0c;各工矿企业关键场所需定期巡检维护。但目前巡检主要靠人工&#xff0c;既耗时费力效率又低&#xff0c;且受环境等因素影响&#xff0c;巡检难以全面规范&#xff0c;隐患或问题易被忽视。在此情况下&#xff0c;如何利用现有…

IoTDB 入门教程 基础篇③——基于Linux系统快速安装启动和上手

文章目录 一、前文二、下载三、解压四、上传五、启动六、执行七、停止八、参考 一、前文 IoTDB入门教程——导读 二、下载 下载二进制可运行程序&#xff1a;https://dlcdn.apache.org/iotdb/1.3.1/apache-iotdb-1.3.1-all-bin.zip 历史版本下载&#xff1a;https://archive.…

Linux Systemd基础教程

一、什么是systemd&#xff1f; systemd是Linux系统的一套基本构建模块。它提供了一个系统和服务管理器&#xff0c;作为PID 1运行并启动系统的其余部分。 systemd提供积极的并行化功能&#xff0c;使用套接字和D-Bus激活来启动服务&#xff0c;提供按需启动守护进程&#xf…

金属表面粗糙度对信号的影响

在进行PCB的传输线设计时&#xff0c;如果希望仿真结果更加贴合于实际的效果&#xff0c;就需要考虑很多的附加因素&#xff0c;比如&#xff0c;真实的叠构参数、介电常数、损耗角正切值、蚀刻因子、金属表面粗糙度、玻纤效应等&#xff0c;在常规的信号仿真中&#xff0c;前三…

Git系列:如何为不同的Git仓库设置不同的配置项?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

如何配置X86应用程序启用大地址模式(将用户态虚拟内存从2GB扩充到3GB),以解决用户态虚拟内存不够用问题?(项目实战案例解析)

目录 1、概述 2、为什么不直接将程序做成64位的&#xff1f; 3、进程内存不足导致程序发生闪退的案例分析 3.1、问题说明 3.2、将Windbg附加到程序进程上进行动态调试 3.3、动态调试的Windbg感知到了中断&#xff0c;中断在DebugBreak函数调用上 3.4、malloc或new失败的…

新手去做抖音小店,这七点千万别忽视!建议收藏!

大家好&#xff0c;我是电商小V 今天咱们就来详细的说一下新手操作抖音小店的几个通病&#xff0c;想要去做抖音小店的小伙伴千万要注意&#xff0c;一定要避免&#xff0c;不要踩坑&#xff0c; 第一点&#xff1a;新手刚去做抖音小店不赚钱的主要原因不是因为你选择了大类目&…