当前位置: 首页 > news >正文

线程的同步

目录

引入

认识条件变量 

快速认识接口​编辑

认识条件变量​编辑

测试代码​编辑

生产消费模型

为何要使用生产者消费者模型

理解 

编写生产消费模型

BlockingQueue

单生产单消费 

多生产多消费 

引入

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步  

认识条件变量 

条件变量是C语言中使用POSIX线程(pthread)库实现线程同步的一种机制。它允许线程在某个条件满足之前进入等待状态,直到其他线程通知它们该条件已改变。

快速认识接口

认识条件变量

测试代码

一次唤醒一个:

一次全部唤醒: 

#include <iostream>
#include <string>
#include<unistd.h>
#include <pthread.h>
using namespace std;
pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;//定义一把全局的锁
pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;//全局条件变量
int num = 5;void* waitt(void*agv){string name=static_cast<const char*>(agv);while(true){pthread_mutex_lock(&gmutex);//加锁 pthread_cond_wait(&gcond,&gmutex);//条件变量等待,这里就是线程等待的位置usleep(10000);cout<<"i am: "<<name<<endl;pthread_mutex_unlock(&gmutex);//解锁}
}
int main()
{pthread_t threads[num];for (int i = 0; i < num; i++)//创建线程{char *name = new char[1024];              // 用来动态分配一个大小为1024字节的字符数组,并将其地址赋给指针namesnprintf(name, 1024, "thread-%d", i + 1); // 注意不用sizeof了因为sizeof(name)为地址字节不是大小了pthread_create(threads + i, nullptr, waitt, (void *)name);usleep(10000);}sleep(1);while(true){//唤醒pthread_cond_broadcast(&gcond);//全部唤醒//pthread_cond_signal(&gcond);//一次一个cout<<"唤醒一个线程......"<<endl;sleep(2);//唤醒的慢些}for (int i = 0; i < num; i++)//线程等待{           pthread_join(threads[i],nullptr);}
}

生产消费模型

为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

理解 

生产消费模型通常是多执行流并发的模型------多个执行流之间怎么进行互斥同步之间如何协同的模型;

供应商和消费者就是线程,超市就是一段内存,方便面就是数据;

未来生产线程将数据放到缓存中,消费者需要的时候从缓存拿;

 思考切入点:“321”原则

                     1.  一个交易场所(特定数据结构形式存在的一段内存空间)

                     2.  两种角色(生产角色,消费角色)--生产线程,消费线程

                     3.  3种关系(生产和生产(竞争关系-互斥),消费和消费(互斥--资源少时就竞争了),生产和消费(互斥--生产者在向缓冲区写入数据时,消费者不能同时从缓冲区中读取数据,以免读取错误数据&&同步--消费者消费数据导致没数据就通知生产者放数据))

实现生产消费模型本质就是:通过代码实现321原则,用锁和条件变量(或其他方式)来实现三种关系!

编写生产消费模型

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程 操作时会被阻塞)

那么可以想到进程间通信时用的管道,一个向管道里写另一个从管道里读。管道如果满了,写进程就要阻塞,如果空了,读进程就要阻塞。管道就是典型的阻塞队列--生产消费模型,只不过使用进程来代替;

单生产单消费 

那么生产和生产以及消费和消费之间的关系就不用维护了;

简单测试代码

blockqueue.hpp:

#pragma once
#include <iostream>
#include <string>
#include <queue> //使用stl的queue
#include <pthread.h>using namespace std;
const static int defaultcap = 5;template <typename T> // 引入模板
class blockqueue
{
private:bool full(){return _block_queue.size() == _max_cap;}bool empty(){return _block_queue.empty();}public:blockqueue(int cap = defaultcap) : _max_cap(cap){pthread_mutex_init(&_mutex, nullptr); // 初始化锁pthread_cond_init(&_p_cond, nullptr); // 初始条件变量pthread_cond_init(&_c_cond, nullptr); // 初始条件变量}void Pop(T *out){                                // 把队列中的数据带出去pthread_mutex_lock(&_mutex); // 用的同一把锁,互斥关系while (empty()){pthread_cond_wait(&_c_cond, &_mutex); // 在该条件变量下等待}// 此时走到这里,1.没有满 || 2.被唤醒了*out = _block_queue.front();_block_queue.pop(); // 拿出数据了pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_p_cond); // 消费一个了那么你就可以生产了,队列有空间了// signal放在unlock之前还是之后都是可以的}// 那么唤醒可以由他们两个互相唤醒互相void equeue(const T &in){                                // 入队列pthread_mutex_lock(&_mutex); // 加锁防止向临界区放数据被打扰while (full()){ // 判断是否为满了// 满了不能生产,必须等待// 此时在临界区里,加锁和解锁之间// pthread_cond_wait在被调用的时候:除了让自己继续排队等待,还会自己释放传入的锁// 函数返回的时候,不就还在临界区么?那么被返回时必须先参与锁的竞争,重新加上锁,该函数才被返回;那么返回时就有锁了pthread_cond_wait(&_p_cond, &_mutex); // 在该条件变量下等待}// 此时走到这里,1.没有满 || 2.被唤醒了_block_queue.push(in); // 生产到阻塞队列里,此时没解锁所以一定至少有一个数据在队列里pthread_mutex_unlock(&_mutex);pthread_cond_signal(&_c_cond); // 生产一个了那么你就可以消费了// 让消费者消费}~blockqueue(){pthread_mutex_destroy(&_mutex); // 将锁销毁pthread_cond_destroy(&_p_cond); // 将局部条件变量销毁pthread_cond_destroy(&_c_cond); // 将局部条件变量销毁}private:queue<T> _block_queue;  // 临界资源int _max_cap;           // 队列最大容量pthread_mutex_t _mutex; // 锁pthread_cond_t _p_cond; // 为生产者提供的条件变量pthread_cond_t _c_cond; // 为消费者提供的条件变量
};

 main.cc:

构建数据(int)

#include"blockqueue.hpp"
#include<pthread.h>
#include<ctime>
#include<unistd.h>void*consumer(void*agv){blockqueue<int>* bq=static_cast<blockqueue<int>* >(agv);while(true){sleep(2);//消费的慢些//1.拿数据int date=0;bq->Pop(&date);//拿//2.处理数据cout<<"consumer-> "<<date<<endl;}
}
void*productor(void*agv){srand(time(nullptr)^getpid());//增加随机性blockqueue<int>* bq=static_cast<blockqueue<int>* >(agv);//两个线程看到同一个阻塞队列while(true){//1.构建数据int date=rand()%10+1;//[1,10]//2.生产数据bq->equeue(date);//入cout<<"producter-> "<<date<<endl;}
}int main(){blockqueue<int>* bq=new blockqueue<int>();//使用int类型pthread_t c,p;pthread_create(&c,nullptr,consumer,bq); pthread_create(&p,nullptr,productor,bq); pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}// #include <iostream>
// #include <string>
// #include<unistd.h>
// #include <pthread.h>
// using namespace std;
// pthread_mutex_t gmutex=PTHREAD_MUTEX_INITIALIZER;//定义一把全局的锁
// pthread_cond_t gcond=PTHREAD_COND_INITIALIZER;//全局条件变量
// int num = 5;// void* waitt(void*agv){
//     string name=static_cast<const char*>(agv);
//     while(true){
//         pthread_mutex_lock(&gmutex);//加锁 
//         pthread_cond_wait(&gcond,&gmutex);//条件变量等待,这里就是线程等待的位置
//             usleep(10000);
//     cout<<"i am: "<<name<<endl;
//     pthread_mutex_unlock(&gmutex);//解锁
//     }
// }
// int main()
// {
//     pthread_t threads[num];
//     for (int i = 0; i < num; i++)//创建线程
//     {
//         char *name = new char[1024];              // 用来动态分配一个大小为1024字节的字符数组,并将其地址赋给指针name
//         snprintf(name, 1024, "thread-%d", i + 1); // 注意不用sizeof了因为sizeof(name)为地址字节不是大小了
//         pthread_create(threads + i, nullptr, waitt, (void *)name);
//         usleep(10000);
//     }
//     sleep(1);
//     while(true){//唤醒
//         pthread_cond_broadcast(&gcond);//全部唤醒
//         //pthread_cond_signal(&gcond);//一次一个
//         cout<<"唤醒一个线程......"<<endl;
//         sleep(2);//唤醒的慢些
//     }//     for (int i = 0; i < num; i++)//线程等待
//     {           
//         pthread_join(threads[i],nullptr);
//     }
// }

main.cc: 

构建任务(task--类)

#include"blockqueue.hpp"
#include"task.hpp"
#include<pthread.h>
#include<ctime>
#include<unistd.h>void*consumer(void*agv){blockqueue<task>* bq=static_cast<blockqueue<task>* >(agv);while(true){sleep(2);//消费的慢些//1.拿数据task t;//无参构造bq->Pop(&t);//拿//2.处理数据t.excute();cout<<"consumer-> "<<t.result()<<endl;}
}
void*productor(void*agv){srand(time(nullptr)^getpid());//增加随机性blockqueue<task>* bq=static_cast<blockqueue<task>* >(agv);//两个线程看到同一个阻塞队列while(true){//1.构建数据int x=rand()%10+1;//[1,10]usleep(10000);//让两个数据尽量不一样,休眠一段时间int y=rand()%10+1;//[1,10]//2.生产数据task t(x,y);//有参构造bq->equeue(t);//入cout<<"producter-> "<<t.debug()<<endl;}
}int main(){blockqueue<task>* bq=new blockqueue<task>();pthread_t c,p;pthread_create(&c,nullptr,consumer,bq); pthread_create(&p,nullptr,productor,bq); pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}

task.hpp:

#pragma once
#include "blockqueue.hpp"
using namespace std;
class task
{
public:task() {}task(int x, int y) : _x(x), _y(y){}void excute(){_result = _x + _y;}string debug(){string msg = to_string(_x) + "+" + to_string(_y) + "=?";return msg;}string result(){string msg = to_string(_x) + "+" + to_string(_y) +"="+ to_string(_result);return msg;}~task(){}private:int _x;int _y;int _result;
};

main.cc:

仿函数

#include "blockqueue.hpp"
#include "task.hpp"
#include <pthread.h>
#include <ctime>
#include <unistd.h>void *consumer(void *agv)
{blockqueue<task_t> *bq = static_cast<blockqueue<task_t> *>(agv);while (true){sleep(2); task_t t;bq->Pop(&t);// 从队列中取出并执行任务t();// 执行任务}
}
void *productor(void *agv)
{srand(time(nullptr) ^ getpid());                             // 增加随机性blockqueue<task_t> *bq = static_cast<blockqueue<task_t> *>(agv); // 两个线程看到同一个阻塞队列while (true){bq->equeue(download);   cout<<"productor->download "<<endl;}
}int main()
{blockqueue<task_t> *bq = new blockqueue<task_t>();pthread_t c, p;pthread_create(&c, nullptr, consumer, bq);pthread_create(&p, nullptr, productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}

task.hpp:

#pragma once
#include<iostream>
#include<functional>
using namespace std;using task_t=function<void()>;//等价于typedef function<void()> task_t;
//定义了一个新的类型别名 task_t,它表示一个接受无参数并返回 void 的函数类型void download(){cout<<"i am a download task"<<endl;
}

多生产多消费 

多生产多消费直接复用上面代码即可;

那么针对生产者不仅仅要将任务放到超市(花费时间),他还要产生任务也要花费时间;

那么针对消费者不仅仅要从超市拿到任务(花费时间),这个任务属于消费者自己了,那么他还要处理任务也要花费时间;

那么我们就不能仅仅只考虑放任务到超市叫生产,拿任务叫消费;

 那么一个生产商再放任务的时候,那么其他生产商有没有正在生产任务呢。

那么放任务和产生任务就并发了;

 那么一个消费者再拿任务的时候,那么其他消费者有没有早都获取了任务正在处理任务呢。

那么拿任务和处理任务就并发了;

如果未来消费处理任务花费时间比较久但是生产任务比较快,那么可以单生产多消费,那么生产任务的时候,一方面有线程在获取另一方面在并发处理任务;

如果未来生产任务花费时间比较久但是消费任务比较快,那么可以多生产单消费;

都很慢就可以多生产多消费;

问题:为什么等待就要在加锁和解锁之间等待呢?

 无论是生产者还是消费者都必须先检测资源的状态,对于生产和消费来说他们要访问公共资源,它不知道资源的条件是否满足。对于生产者来说希望队列有空间,对于消费者来说希望队列有数据。可是对于他们来说他们并不知道,只有他们查一次才知道,而查这一行为本身就是访问,就决定了查之前就要加锁,并且检测可能满足可能不满足,注定了必须在临界区里等待因为判定结果是在临界区里的;

相关文章:

线程的同步

目录 引入 认识条件变量 快速认识接口​编辑 认识条件变量​编辑 测试代码​编辑 生产消费模型 为何要使用生产者消费者模型 理解 编写生产消费模型 BlockingQueue 单生产单消费 多生产多消费 引入 同步&#xff1a;在保证数据安全的前提下&#xff0c;让线程…...

【启明智显分享】ZX7981PG/ZX7981PM融入官方OpenWrt,启明智显SDK/官方OpenWrt任由选择!

好消息&#xff01;好消息&#xff01;启明智显ZX7981PG和ZX7981PM正式融入官方 OpenWrt 的大家庭啦&#xff01;现在开发者不仅可以基于启明智显的SDK进行二次开发&#xff0c;还可以直接应用官方OpenWrt以及我们的开源资料进行开发&#xff01; 借助OpenWrt的强大生态&…...

如何用java发送包含表格形式的邮件

问题&#xff1a; 如何用java发送包含表格形式的邮件&#xff1f; 方法&#xff1a; 发用freemaker工具来替换html的表格变量&#xff0c;从而动态生成了html。然后再发送这个html格式&#xff08;不能用纯文本&#xff09;文本即可。 优化流程&#xff1a; 1、准备模板&#x…...

讲个故事:关于一次接口性能优化的心里路程

这是一个程序猿写的第一个故事&#xff0c;请各位懂行的客官静下心来&#xff0c;慢慢品读。就知道我为什么要单独写一个文章来记录这次过程了&#xff0c;因为实在是太坎坷了...... 背景介绍 近期项目投产时遇到一个问题&#xff0c;投产后在验证时发现大部分用户系统登…...

Centos7升级到openssh9.9

openssh9.9 是2024.9.20出的最新版ssh。因为客户扫描出一大堆centos7的漏洞&#xff0c;全是这个openssh的&#xff0c;好多补丁&#xff0c;所以索性升级到最新版。 需要自己制作rpm包&#xff0c;这个我是不懂&#xff0c;照这个来&#xff1a; Linux服务器升级openssh9.9最…...

使用 STM32F407 串口实现 485 通信

准备工作 了解485通信基本概念与原理&#xff1a;RS485通信详解_485通讯de接什么口-CSDN博客 安装编译软件&#xff1a;keil uVision 5.6 软件资料&#xff1a;STM32CubeF4 固件包&#xff0c;正点原子RS485通信例程 ​​​​​​​参考视频&#xff1a;第26讲 基础篇-新建H…...

基于NERF技术重建学习笔记

NeRF&#xff08;Neural Radiance Fields&#xff09;是一种用于3D场景重建的神经网络模型&#xff0c;能够从2D图像生成逼真的3D渲染效果。它将场景表征为一个连续的5D函数&#xff0c;利用了体积渲染和神经网络的结合&#xff0c;通过学习光线穿过空间时的颜色和密度来重建场…...

webView 支持全屏播放

webView 支持全屏播放 直接上代码 public class CustomFullScreenWebViewClient extends WebChromeClient {WebView webView;Context context;/*** 视频全屏参数*/protected static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS new FrameLayout.LayoutParams(ViewG…...

QGIS之三十二DEM地形导出三维模型gltf

效果 1、准备数据 (1)dem.tif (2)dom.tif 2、qgis加载dem和dom数据 3、安装插件 插件步骤可以参考这篇文章 QGIS之二十四安装插件 安装了Qgis2threejs插件,结果...

【python爬虫】携程旅行景点游客数据分析与可视化

一.选题背景 随着旅游业的快速发展&#xff0c;越来越多的人选择通过互联网平台预订旅行产品&#xff0c;其中携程网作为国内领先的在线旅行服务提供商&#xff0c;拥有大量的旅游产品和用户数据。利用爬虫技术可以获取携程网上各个景点的游客数据&#xff0c;包括游客数量、游…...

python实现onvif协议下控制摄像头变焦,以及融合人形识别与跟踪控制

#1024程序员节 | 征文# 这两天才因为项目需要&#xff0c;对网络摄像头的视频采集以及实现人形识别与跟踪技术。对于onvif协议自然起先也没有任何的了解。但是购买的摄像头是SONY网络头是用在其他地方的。因为前期支持探究项目解决方案&#xff0c;就直接拿来做demo测试使用。 …...

【Vue】Vue3.0(十四)接口,泛型和自定义类型的概念及使用

上篇文章&#xff1a; 【Vue】Vue3.0&#xff08;十三&#xff09;中标签属性ref&#xff08;加在普通标签上、加在组件标签上&#xff09;、局部样式 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&…...

【C++】红黑树万字详解(一文彻底搞懂红黑树的底层逻辑)

目录 00.引入 01.红黑树的性质 02.红黑树的定义 03.红黑树的插入 1.按照二叉搜索树的规则插入新节点 2.检测新节点插入后&#xff0c;是否满足红黑树的性质 1.uncle节点存在且为红色 2.uncle节点不存在 3.uncle节点存在且为黑色 04.验证红黑树 00.引入 和AVL树一样&am…...

开源FluentFTP实操,操控FTP文件

概述&#xff1a;通过FluentFTP库&#xff0c;轻松在.NET中实现FTP功能。支持判断、创建、删除文件夹&#xff0c;判断文件是否存在&#xff0c;实现上传、下载和删除文件。简便而强大的FTP操作&#xff0c;提升文件传输效率。 在.NET中&#xff0c;使用FluentFTP库可以方便地…...

论文解读 | ECCV2024 AutoEval-Video:一个用于评估大型视觉-语言模型在开放式视频问答中的自动基准测试...

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 点击 阅读原文 观看作者讲解回放&#xff01; 作者简介 陈修元&#xff0c;上海交通大学清源研究院硕士生 概述 总结来说&#xff0c;我们提出了一个新颖且具有挑战性的基准测试AutoEvalVideo&#xff0c;用于全…...

postgresql14主从同步流复制搭建

1. 如果使用docker搭建请移步 Docker 启动 PostgreSQL 主从架构&#xff1a;实现数据同步的高效部署指南_docker安装postgresql主从同步-CSDN博客 2. 背景 pgsql版本&#xff1a;PostgreSQL 14.13 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4…...

企业信息化管理中的数据集成方案:销售出库单对接

企业信息化管理中的数据集成方案&#xff1a;销售出库单对接 销售出库单旺店通→金蝶&#xff1a;高效数据集成案例分享 在企业信息化管理中&#xff0c;数据的高效流动和准确对接是实现业务流程自动化的关键。本文将聚焦于一个具体的系统对接集成案例&#xff1a;如何将旺店通…...

3.cpp基本数据类型

cpp基本数据类型 1.cpp基本数据类型 1.cpp基本数据类型 C基本数据类型和C语言的基本数据类型差不多 注意bool类型&#xff1a;存储真值 true 或假值 false&#xff0c;C语言编译器C99以上支持。 C语言的bool类型&#xff1a;要添加 #include <stdbool.h>头文件 #includ…...

MCK主机加固与防漏扫的深度解析

在当今这个信息化飞速发展的时代&#xff0c;网络安全成为了企业不可忽视的重要议题。漏洞扫描&#xff0c;简称漏扫&#xff0c;是一种旨在发现计算机系统、网络或应用程序中潜在安全漏洞的技术手段。通过自动化工具&#xff0c;漏扫能够识别出系统中存在的已知漏洞&#xff0…...

《软件估算之原始功能点:精准度量软件规模的关键》

《软件估算之原始功能点&#xff1a;精准度量软件规模的关键》 一、软件估算的重要性与方法概述二、原始功能点的构成要素&#xff08;一&#xff09;数据功能&#xff08;二&#xff09;事务功能 三、原始功能点的估算方法&#xff08;一&#xff09;功能点分类估算&#xff0…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

电脑桌面太单调,用Python写一个桌面小宠物应用。

下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡&#xff0c;可以响应鼠标点击&#xff0c;并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...

使用VMware克隆功能快速搭建集群

自己搭建的虚拟机&#xff0c;后续不管是学习java还是大数据&#xff0c;都需要集群&#xff0c;java需要分布式的微服务&#xff0c;大数据Hadoop的计算集群&#xff0c;如果从头开始搭建虚拟机会比较费时费力&#xff0c;这里分享一下如何使用克隆功能快速搭建一个集群 先把…...

Web APIS Day01

1.声明变量const优先 那为什么一开始前面就不能用const呢&#xff0c;接下来看几个例子&#xff1a; 下面这张为什么可以用const呢&#xff1f;因为复杂数据的引用地址没变&#xff0c;数组还是数组&#xff0c;只是添加了个元素&#xff0c;本质没变&#xff0c;所以可以用con…...