C++并发编程

news2024/9/23 1:26:45

目录

  • 一、并发编程相关的基础概念
    • 1、操作系统(Linux)
    • 2、任务和通信
    • 3、多进程和多线程
    • 4、C++中的多线程发展史
  • 二、pthread线程使用讲解和实战
    • 1、pthread基本使用
    • 2、线程的分离
    • 3、线程属性
    • 4、关于线程的几个值得注意的点
  • 三、线程的同步之互斥锁、读写锁、非阻塞式锁和条件变量
    • 1、线程同步的必要性
    • 2、互斥锁mutex
    • 3、读写锁
    • 4、非阻塞式锁
    • 5、条件变量
  • 四、标准库的thread基本使用
    • 1、标准库中线程支持
    • 2、std::thread的使用和案例分析
    • 3、管理当前线程的函数
  • 五、thread的线程同步
    • 1、mutex
    • 2、condition_variable
  • 六、thread的异步机制future
  • 七、C++20新引入的jthread

一、并发编程相关的基础概念

1、操作系统(Linux)

(1)内核和应用
  在Linux程序开发中,涉及到了许多的并发操作,根据程序的上下层关系可分为两部分,一部分是在内核中,即操作系统本身的在运行过程中就包含有大量的并发操作,如硬件驱动程序和内存管理以及进程调度模块就在同时运行着;另一部分就是我们经常说的应用程序,运行在linux系统之上的程序,如一个服务器应用程序,同时存在两个线程在运行,一个负责监听客户端,一个负责处理客户端的请求;

(2)进程和线程
  进程是系统进行资源分配的基本单位,线程是CPU进行调度的基本单位。一个进程中包含多个线程。有关于进程线程更多的区别和细节请自行百度,这里不再赘述。也可阅读我的另一个专栏《Linux IO编程和网络编程入门》;

(3)并行和串行,宏观和微观
  并行简而言之就是可以同一时间干很多件事情;串行就是有先后顺序,一件事情结束以后才能去做另一件事情。软件开发中的并行和串行也是如此,将一件事情替换为一个程序或者一个进程又或一个线程,能否同时运行;

  多个线程只有在多核处理器才能真正的同时运行,在单个处理器上只能做到假并行,看起来是在同时运行,实际上是调度器以时间片为单位切换调度执行多个线程,某一时刻只有一个程序在运行;

(4)系统调用,POSIX API,函数库、框架库

已经有人讲的很清楚了,我就不班门弄斧、制造垃圾了:
https://dandelioncloud.cn/article/details/1555512222984392706

(5)阻塞和非阻塞
  阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用结果返回前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前线程。因此对应非阻塞的情况,调用者需要定时轮询查看处理状态。
---------------来源于百度百科

2、任务和通信

(1)进程间通信IPC与线程间通信

进程间通信的方式:无名管道( pipe )、高级管道(popen)、有名管道(named pipe)、
消息队列( message queue )、信号量( semophore ) 、信号 ( sinal ) 、共享内存、
( shared memory ) 、套接字( socket )

线程间通信的方式:互斥锁、读写锁、自旋锁、条件变量、信号机制、信号量机制

(2)同步和异步
  同步就相当于是 当客户端发送请求给服务端,在等待服务端响应的请求时,客户端不做其他的事情。当服务端做完了才返回到客户端。这样的话客户端需要一直等待。用户使用起来会有不友好。

  异步就是,当客户端发送给服务端请求时,在等待服务端响应的时候,客户端可以做其他的事情,这样节约了时间,提高了效率。

  也可以认为,同步就是双方约定了一个固定的频率进行某件事情,如每天两点见面,则在两点的时候就不能去做其他事情了;而异步则是双方见面的时间不固定,想见了就见,没有固定约定的时间。

3、多进程和多线程

(1)如何选择使用多进程还是多线程

https://www.cnblogs.com/mude918/p/11750350.html

(2)单核和对称多核SMP下的多线程
  只有在多核下,才可以实现多个线程同时运行,在单核上只能实现伪并行;

4、C++中的多线程发展史

(1)C++98中没有并发支持,因为C中也没有并发支持,早期C++认为这不是语言该管的事儿

(2)POSIX OS的pthread被广泛用于C/C++的多线程编程

(3)这造成很大问题是:很多C++程序员根本没有并发编程的意识,需要时也只能盲目胡乱找资源

(4)Java在语言层面源生支持并发,取得了很大的成功和很好的反响

(5)C++11中开始引入并发编程机制std::thread

二、pthread线程使用讲解和实战

  对于二、三部分请阅读我之前写的一篇博客《linux线程全解
》,在本篇就不详述了,避免重复性工作。

  pthread与操作系统和编程语言无关,是符合posix标准的操作系统都具有的API

1、pthread基本使用

(1)ubuntu系统中进行 man 手册安装:
sudo apt-get install glibc-doc manpages-posix manpages-posix-dev

(2)头文件:#include <pthread.h>

(3)链接时添加:-lpthread

2、线程的分离

(1)线程有2中状态:JOINABLE或者DETACHED,默认是JOINABLE

(2)JOINABLE的线程必须在创建它的线程中使用pthread_join回收,否则会有资源未释放

(3)DETACHED的线程可以在终止时释放资源,这样创建它的线程就不用通过pthread_join来等待接收

(4)线程转为DETACHED有2种方法:第一种是线程函数内自己调用pthread_detach(pthread_self());

3、线程属性

(1)pthread_attr_t attr;//声明一个参数
(2)pthread_attr_init(&attr);//对参数进行初始化
(3)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);//设置线程为可连接的
(4)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置线程为可分离的
(5)pthread_attr_destroy(&attr)//销毁属性,防止内存泄漏
(6)int pthread_attr_getdetachstate(pthread_attr_t *attr,int *detachstate);获取线程状态

4、关于线程的几个值得注意的点

(1)main所在的线程称之为“初始线程”,从main返回的时候,整个进程都被终止了;

(2)在任意线程内调用exit函数会让该线程所在的进程整个退出。所以主动退出线程的时候一定要使用pthread_exit函数,而不是exit;

(3)当主线程调用pthread_exit函数仅仅只是终止主线程,其他线程仍将继续存在;

三、线程的同步之互斥锁、读写锁、非阻塞式锁和条件变量

1、线程同步的必要性

  当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。

  举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?
  (1)账户余额是0,取钱不成功;(2)账户余额是100,取钱成功了。那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题,使得在同一时刻只有一个动作可以作用于这个对象身上;

2、互斥锁mutex

(1)互斥锁静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2)互斥锁动态初始化:pthread_mutex_init(&mutex,NULL);
(3)上锁和解锁:pthread_mutex_lock(&mutex);	pthread_mutex_unlock(&mutex);
(4)互斥锁销毁:pthread_mutex_destroy(&mutex);

3、读写锁

pthread_rwlock_t
初始化:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
销毁:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
进行读操作上锁:int pthread_rwlock_rdlock(pthread_rwlock_t  *rwlock);
进行写操作上锁:int pthread_rwlock_wrlock(pthread_rwlock_t  *rwlock);
解锁:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

4、非阻塞式锁

(1)互斥锁非阻塞式上锁:pthread_mutex_trylock(&mutex);
(2)读写锁非阻塞式上锁:
	int pthread_rwlock_trywrlock(pthread_rwlock_t  *rwlock);
	int pthread_rwlock_tryrdlock(pthread_rwlock_t  *rwlock);

5、条件变量

(1)条件变量的核心功能:A线程等待条件时阻塞wait,B线程必要时signal唤醒A
(2)条件变量实现了多个线程之间的同步
(3)条件变量常用于所谓的“生产者与消费者模型”
(4)条件变量相关API

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
pthread_mutex_lock(&mutex);/*锁住互斥量*/
pthread_cond_signal(&cond);//发送信号量 跟wait函数不在同一个线程中
pthread_cond_wait(&cond,&mutex);//阻塞线程,等待条件变量,同时解锁互斥量
pthread_mutex_unlock(&mutex);//解锁互斥量
pthread_mutex_destroy(&mutex);//销毁互斥锁
pthread_cond_destroy(&cond);//销毁条件变量

(5)条件变量的实例

参考:https://blog.csdn.net/chengonghao/article/details/51779279

四、标准库的thread基本使用

1、标准库中线程支持

(1)参考:https://zh.cppreference.com/w/cpp/thread
(2)C++11的实现是主体,C++20只是增加了扩展

2、std::thread的使用和案例分析

构造函数和传参:https://zh.cppreference.com/w/cpp/thread/thread/thread

类 thread 表示单个执行线程。线程允许多个函数同时执行。在头文件 <thread> 定义;

线程在构造关联的线程对象时立即开始执行(等待任何OS调度延迟),从提供给作为构造函数
参数的顶层函数开始。顶层函数的返回值将被忽略,而且若它以抛异常终止,则调用
 std::terminate 。顶层函数可以通过 std::promise 或通过修改共享变量(可能需要同
 步,见 std::mutex 与 std::atomic )将其返回值或异常传递给调用方。

std::thread 对象也可能处于不表示任何线程的状态(默认构造、被移动、 detach 或 
join 后),并且执行线程可能与任何 thread 对象无关( detach 后)。

没有两个 std::thread 对象会表示同一执行线程; std::thread 不是可复制构造 
(CopyConstructible) 或可复制赋值 (CopyAssignable) 的,尽管它可移动构造 
(MoveConstructible) 且可移动赋值 (MoveAssignable)

在这里插入图片描述

// 左值引用
int num = 10;
int &b = num;     // 正确
int &c = 10;      // 错误
 
int num = 10;
const int &b = num;   // 正确
const int &c = 10;    // 正确
 
 
// 右值引用
int num = 10;
//int && a = num;    // 错误,右值引用不能初始化为左值
int && a = 10;       // 正确
 
a = 100;
cout << a << endl;   // 输出为100,右值引用可以修改值
 
 
// 右值引用的使用
// 如 thread argv 的传入
template<typename _Callable, typename... _Args>
explicit thread(_Callable&& __f, _Args&&... __args) { 
//.... 
}
// Args&&... args 是对函数参数的类型 Args&& 进行展开
// args... 是对函数参数 args 进行展开
// explicit 只对构造函数起作用,用来抑制隐式转换

在这里插入图片描述

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
 
void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 1 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
class foo
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 3 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
 
class baz
{
public:
    void operator()()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 4 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
 
int main()
{
    int n = 0;
    foo f;
    baz b;
    std::thread t1; // t1 不是线程
    std::thread t2(f1, n + 1); // 按值传递
    std::thread t3(f2, std::ref(n)); // 按引用传递
    std::thread t4(std::move(t3)); // t4 现在运行 f2() 。 t3 不再是线程
    std::thread t5(&foo::bar, &f); // t5 在对象 f 上运行 foo::bar()
    std::thread t6(b); // t6 在对象 b 的副本上运行 baz::operator()
    t2.join();
    t4.join();
    t5.join();
    t6.join();
    std::cout << "Final value of n is " << n << '\n';
    std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';
    std::cout << "Final value of b.n (baz::n) is " << b.n << '\n';
}
可能的输出:

Thread 1 executing
Thread 2 executing
Thread 3 executing
Thread 4 executing
Thread 3 executing
Thread 1 executing
Thread 2 executing
Thread 4 executing
Thread 2 executing
Thread 3 executing
Thread 1 executing
Thread 4 executing
Thread 3 executing
Thread 2 executing
Thread 1 executing
Thread 4 executing
Thread 3 executing
Thread 1 executing
Thread 2 executing
Thread 4 executing
Final value of n is 5
Final value of f.n (foo::n) is 5
Final value of b.n (baz::n) is 0

3、管理当前线程的函数

定义于命名空间 this_thread
	yield(C++11)建议实现重新调度各执行线程(函数)
	get_id (C++11)返回当前线程的线程 id(函数)
	sleep_for (C++11)使当前线程的执行停止指定的时间段(函数)
	sleep_until (C++11)使当前线程的执行停止直到指定的时间点(函数)

五、thread的线程同步

1、mutex

RAII风格:https://zh.cppreference.com/w/cpp/language/raii

	资源获取即初始化(Resource Acquisition Is Initialization),或称 RAII,是
一种 C++ 编程技术,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开
的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事
物)的生命周期绑定与一个对象的生存期相绑定。

在这里插入图片描述

2、condition_variable

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    // 等待直至 main() 发送数据
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{return ready;});
 
    // 等待后,我们占有锁。
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    // 发送数据回 main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    // 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
    lk.unlock();
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    // 发送数据到 worker 线程
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // 等候 worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
}
输出:

main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing

六、thread的异步机制future

参考阅读:https://zh.cppreference.com/w/cpp/thread/future

在这里插入图片描述

七、C++20新引入的jthread

阅读参考:https://www.zhihu.com/question/364140779/answer/959369984
1)std::jthread与std::thread的区别是什么?
	据我所知,特性上,std::jthread相比std::thread主要增加了以下两个功能:
	1.std::jthread对象被destruct时,会自动调用join,等待其所表示的执行流结束。
	2.支持外部请求中止(通过get_stop_source、get_stop_token和request_stop)。

(2)为什么不是选择往std::thread添加新接口,而是引入了一个新的标准库?
因为std::jthread为了实现上述新功能,带来了额外的性能开销(主要是多了一个成员变量)
。而根据C++一直以来“不为不使用的功能付费”的设计哲学,他们自然就把这些新功能拆出来
新做了一个类。

关于《C++并发编程》后续还会增加章节的,这篇文章讲的很简略,更多的是让大家知道C++关于并发编程的一些库,避免后续工作或者学习中看到相关代码不知道来自于那里。本篇文章类似于预习课文的意思吧

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历、百度百科以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

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

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

相关文章

MySQL 数据库主从复制

参考视频教程: https://www.bilibili.com/video/BV13a411q753?p172 参考博客: https://blog.csdn.net/main_Scanner01/article/details/124259050 1. 介绍 1.1 要点 主库(master): 负责增删改 从库(slave) : 负责查询 主从库关系: 一对多 1.2 步骤 ● master将改变记录到_…

宝塔部署node项目

宝塔linux&#xff1a;是一个linux运维面板&#xff0c;方便管理服务器。 安装服务器默认宝塔面板 使用步骤 到防火墙页面开启8888端口&#xff1a; 点击进入防火墙远程登录服务器。复制面板安全入口查询命令&#xff0c;获取面板安全入口。sudo /etc/init.d/bt default | gr…

快速入门Docker

目录 Docker简介 Dokcer环境配置 Docker HelloWorld运行原理解析 阿里云镜像仓库 常用命令 基本命令 镜像命令 容器命令 Docker简介 Docker 是基于Go语言实现的一个开源项目&#xff0c;通过对应组建的封装&#xff0c;分发&#xff0c;部署&#xff0c;运行等生命周期的管…

如何创建多语言WordPress网站[专家建议]

如果您想有效地接触广泛的国际受众&#xff0c;那么您应该考虑制作一个多语言WordPress网站。您知道吗&#xff0c;61.1% 的网站是英文的&#xff0c;但全球只有 25.9% 的用户实际上可以在线使用这种语言。 多语言功能意味着创建站点的两个或多个语言实例&#xff0c;在大多数…

anaconda安装pytorch

1.需不需要另外创建一个新环境 答案是&#xff1a;可以不需要 所以我的做法是直接在base环境中安装的pytorch 2.python、torch、torchvision版本对应 我的是python3.9&#xff0c;安装的是cuda11.3–torch1.10.1&#xff0c;所以按理来说torchvision为0.11.2 3.离线安装tor…

P4的exercises实现原理

今天介绍一下p4的tutorials-master\utils如何将tutorials-master\exercises下各个小实验的实现&#xff0c;有助于以后自己构建mininet并配置p4交换机&#xff0c;通过本博客可以简单了解&#xff1a; Makefile是如何实现相关的实验的基于p4的mininet如何搭建 1.Makefile干了什…

python-(6-4-3)爬虫---re解析案例

文章目录一 需求二 总体思路1&#xff09;从网址定位到2022必看热片2&#xff09;从2022必看热片定位到子页面的链接地址3&#xff09;请求子页面的链接地址&#xff0c;拿到我们想要的下载地址三 分析步骤1&#xff09;从网址定位到2022必看热片2&#xff09;从2022必看热片定…

开关电源环路稳定性分析(05)-传递函数

大家好&#xff0c;这里是大话硬件。 经过前面4篇文章的梳理&#xff0c;估计很多人已经等不及了&#xff0c;什么时候可以开始环路的分析。为了尽快进入到大家关心的部分&#xff0c;这一讲我们正式进入环路分析的第一部分——传递函数。 传递函数&#xff0c;简单的理解就是…

立体车库管理系统

1、立体车库主界面 左侧为菜单栏中间为监视页面上方为实时报警页面&#xff0c;上方第二页为设备指令&#xff0c;第三页为升降机监测&#xff0c;第三页为穿梭车监测2、立体车库设备状态显示 通用状态显示升降机1#2#和穿梭车1#2#3#的缓存指令编号&#xff0c;点击按钮可以清楚…

【设计模式】32.结构型模式-组合模式(Composite)

一、描述 首先&#xff0c;看一个数据结构&#xff1a; 在平时开发过程中&#xff0c;我们的菜单目录、文件夹目录等都有类似如上的实体结构&#xff0c;其中composite代表父级节点&#xff0c;leaf代表叶子节点&#xff0c;composite可以有子节点&#xff0c;但是leaf下没有…

一文读懂ChatGPT模型原理

&#xff08;本文是ChatGPT原理介绍&#xff0c;但没有任何数学公式&#xff0c;可以放心食用&#xff09; 前言 这两天&#xff0c;ChatGPT模型真可谓称得上是狂拽酷炫D炸天的存在了。一度登上了知乎热搜&#xff0c;这对科技类话题是非常难的存在。不光是做人工智能、机器学习…

ORB-SLAM2 ---- Tracking::TrackWithMotionModel函数

目录 1.函数作用 2.步骤 3.code 4.函数解释 4.1 更新上一帧的位姿&#xff1b;对于双目或RGB-D相机&#xff0c;还会根据深度值生成临时地图点 4.2 根据之前估计的速度&#xff0c;用恒速模型得到当前帧的初始位姿。 4.3 用上一帧地图点进行投影匹配&#xff0c;如果匹…

JDK版本对应其major.minor version,看这一篇就够啦(附java历史版本下载地址)

文章目录前言JDK版本对应其major versionJDK历史版本下载地址前言 今天博主在学习SpringBoot&#xff0c;启动项目时遇到这样一个问题 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project springbo…

[附源码]JAVA毕业设计企业信息安全评价系统(系统+LW)

[附源码]JAVA毕业设计企业信息安全评价系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

[附源码]Python计算机毕业设计Django小区疫情事件处理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Git学习笔记 Git Gitee GitHub GitLab

目录 Git GitHub Gitee码云 GitLab Git 概述 何为版本控制 为什么需要版本控制 集中式版本控制工具 分布式版本控制工具 Git和代码托管中心 Git命令 git init命令 git config git status 查看 git 状态 git rm --cached xx git commit -m “备注” xx git reflo…

PHP代码审计系列(二)

PHP代码审计系列&#xff08;二&#xff09; 本系列将收集多个PHP代码安全审计项目从易到难&#xff0c;并加入个人详细的源码解读。此系列将进行持续更新。 strcmp比较字符串 源码如下 <?php $flag "flag"; if (isset($_GET[a])) { if (strcmp($_GET[a], …

DBCO-PEG3-Maleimide,Mal-PEG3-DBCO,二苯并环辛炔-三聚乙二醇-马来酰亚胺

​ 中英文名&#xff1a; CAS号&#xff1a;N/A| 英文名&#xff1a;DBCO-PEG3-Maleimide&#xff0c;Mal-PEG3-DBCO |中文名&#xff1a;二苯并环辛炔-三聚乙二醇-马来酰亚胺物理参数&#xff1a; CASNumber&#xff1a;N/A Molecular formula&#xff1a;C34H38N4O8 Molecul…

[附源码]计算机毕业设计绿色生鲜Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

一键免密登录云平台!ZStack Cloud 4.5.0等你来解锁……

近日&#xff0c;ZStack Cloud 4.5.0发布&#xff0c;新增支持多种标准单点登录&#xff08;SSO&#xff09;协议。云平台现可对接OIDC/OAuth2/CAS三种协议的统一身份认证系统&#xff0c;使认证系统中的用户可一键免密登录云平台&#xff0c;大大提高了云平台的访问效率和安全…