【Linux C | 多线程编程】线程同步 | 信号量(无名信号量) 及其使用例子

news2025/1/12 13:46:49

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰:

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
    • ✨1.1 二值信号量、计数信号量
    • ✨1.2 System V信号量、POSIX信号量
  • 🎄二、无名信号量
    • ✨2.1 初始化无名信号量 | sem_init
    • ✨2.2 销毁无名信号量 | sem_destroy
    • ✨2.3 等待信号量 | sem_wait
    • ✨2.4 发布信号量 | sem_post
    • ✨2.5 获取信号量的值 | sem_getvalue
  • 🎄三、二值信号量的使用例子
    • ✨3.1 信号量在临界区的使用
    • ✨3.2 信号量在“生产者-消费者”模式的使用
  • 🎄四、计数信号量的使用例子
  • 🎄五、总结


在这里插入图片描述

🎄一、概述

信号量是由E.W.Dijkstra为互斥和同步的高级管理提出的概念。它支持两种原子操作,一个是wait操作(减少信号量的值),另一个是post操作(增加信号量的值)。

一般来说, 信号量是和某种预先定义的资源相关联的。信号量元素的值,表示与之关联的资源的个数。内核会负责维护信号量的值,并确保其值不小于0。


✨1.1 二值信号量、计数信号量

信号量按照初始化的信号量值,可以分为使用二值信号量(binary semaphore)和计数信号量(counting semaphore)

  • 二值信号量:是使用最广泛的信号量。 对于这种信号量而言,它只有两种合法值:0和1,对应一个可用的资源。若当前有资源可用,则与之对应的二值信号量的值为1;若资源已被占用,则与之对应的二值信号量的值为0。
  • 计数信号量:资源个数超过1个的信号量。假设计数信号量初始化的信号量值为5,表示该信号量有6中合法值:0、1、2、3、4、5。当取值为0时,表示没有资源可用了;其他合法值则表示资源的剩余数量。

✨1.2 System V信号量、POSIX信号量

Linux系统中提供了两个信号量实现,一种是System V信号量,另一种是POSIX信号量,它们的作用是相同的,都是用于同步进程之间及线程之间的操作,以达到无冲突地访问共享资源的目的。

下面是System V信号量的相关接口函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd,/* union semun arg*/);
int semop(int semid, struct sembuf *sops, unsigned nsops);

POSIX信号量提供了两类: 有名信号量和无名信号量。
有名信号量由于其有名字, 多个不相干的进程可以通过名字来打开同一个信号量, 从而完成同步操作, 所以有名信号量的操作要方便一些, 适用范围也比无名信号量更广。
有名信号量的函数接口与无名信号量基本相同,就是初始化和销毁有区别,下面是有名信号量的初始化、销毁接口:

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
sem_close(sem_p);
sem_unlink(sem_p);

而无名信号量,由于没有名字多用于线程之间,也是本文重点节点的信号量,下文都是所说的信号量,都特指这种用于多线程同步的无名信号量


在这里插入图片描述

🎄二、无名信号量

无名信号量, 又称为基于内存的信号量,由于其没有名字,没法通过open操作直接找到对应的信号量,所以很难直接用于没有关联的两个进程之间。无名信号量多用于线程之间的同步。因为线程会共享地址空间, 所以访问共同的无名信号量是很容易办到的事情。

✨2.1 初始化无名信号量 | sem_init

无名信号量的初始化是通过sem_init函数来完成的。

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
Link with -pthread.
  • 函数描述:初始化sem指针指向的无名信号量。
  • 函数参数:
    • sem:要初始化的无名信号量地址;
    • pshared:用于声明信号量是在线程间共享还是在进程间共享。0表示在线程间共享,非零值则表示信号量将在进程间共享。 要想在进程间共享,信号量必须位于共享内存区域内。
    • value:指定的信号量初始值。
  • 返回值:成功返回 0, 失败返回 -1 并设置errno。

✨2.2 销毁无名信号量 | sem_destroy

销毁无名信号量的接口定义如下:

#include <semaphore.h>
int sem_destroy(sem_t *sem);
  • 函数描述:销毁sem指针指向的无名信号量。必须是sem_init函数初始化过的。
  • 函数参数:
    • sem:要销毁的无名信号量地址;
  • 返回值:成功返回 0, 失败返回 -1 并设置errno。

✨2.3 等待信号量 | sem_wait

信号量总是和某种资源关联在一起,申请资源时,需要先调用sem_wait函数。函数原型如下:

#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
  • 函数描述:这三个函数都是用于等待信号量, 它会将信号量的值减1。如果函数正处于阻塞,被信号中断,则返回-1,并且置errno为EINTR。
    • sem_wait:若信号量值大于0, 那么sem_wait函数将信号量的值减1之后会立刻返回。否则sem_wait函数陷入阻塞,待信号量的值大于0之后,再执行减1操作,然后成功返回。
    • sem_trywait:若信号量值大于0,那么sem_trywait函数将信号量的值减1之后会立刻返回。否则sem_trywait立刻返回失败, 并置errnoEAGAIN
    • sem_timedwait:若信号量值大于0,那么sem_timedwait函数将信号量的值减1之后会立刻返回。否则sem_timedwait会等待一段时间,如果超过了等待时间,信号量的值仍为0,那么返回 -1,并置errnoETIMEOUT
  • 函数参数:
    • sem:要等待的无名信号量地址;
    • abs_timeout:是一个绝对时间,可以使用gettimeofday函数或clock_gettime函数获取当前时间,再加上想等待的时间,最后将相加的值转换成struct timespec类型传给 sem_timedwait
  • 返回值:成功返回 0, 失败返回 -1 并设置errno。

✨2.4 发布信号量 | sem_post

前面介绍了信号量申请资源时要调用的函数,这小节介绍归还资源时信号量调用的函数 sem_post ,函数原型如下:

#include <semaphore.h>
int sem_post(sem_t *sem);
  • 函数描述:用于发布信号量,表示已经完成了对资源使用,可以归还资源了。
    如果发布信号量之前, 信号量的值是0,并且已经有线程正等待在该信号量上,调用sem_post之后,会有一个线程被唤醒,被唤醒的线程会继续sem_wait函数的减1操作。 如果有多个线程正等待在信号量上,那么将无法确认哪个线程会被唤醒。
  • 函数参数:
    • sem:要发布的无名信号量地址;
  • 返回值:成功返回 0, 失败返回 -1 并设置errno。
    参数指向非法的信号量地址时,会置errno为EINVAL。
    当信号量的值超过上限(即超过INT_MAX)时,置errnoEOVERFLOW

✨2.5 获取信号量的值 | sem_getvalue

信号量的值可以通过 sem_getvalue 获取,函数原型如下:

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
  • 函数描述:sem_getvalue函数会返回当前信号量的值, 并将值写入sval指向的变量.
    如果值大于0,表示不需要等待;如果值为0,表示再申请资源时需要等待。这个值不会为负数,并且其返回的值可能已经过时了
  • 函数参数:
    • sem:要获取值的无名信号量地址;
    • sval:传出参数,用于存放信号量值的int型地址。
  • 返回值:成功返回 0, 失败返回 -1 并设置errno。

在这里插入图片描述

🎄三、二值信号量的使用例子

首先了解一下什么是临界区,所谓临界区, 是指同一时间只能容许一个线程进入的一系列操作。

二值信号量是最常用的信号量,在Linux多线程编程中,二值信号量主要有两种用法:一是可以像互斥量一样,对临界区加锁,防止多个线程并发进入临界区。二是可以像条件变量一样,在“生产者-消费者”模式的多个线程进行同步地访问共享资源。

✨3.1 信号量在临界区的使用

下面代码是使用信号量来加锁临界区,使多个线程不会并发地进入临界区操作。这个用法看起来很像互斥量。

// 10_sem_mutex.c
// gcc 10_sem_mutex.c -l pthread
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int g_Count = 0;
sem_t g_sem;
void *func(void *arg)
{
	int i=0;
	for(i=0; i<10000000; i++)
	{
		sem_wait(&g_sem);
		g_Count++;
		sem_post(&g_sem);
	}
	return NULL;
}

int main()
{
	sem_init(&g_sem, 0, 1);
	// 创建4个线程
	pthread_t threadId[4];
	int i=0;
	for(i=0; i<4; i++)
	{
		pthread_create(&threadId[i], NULL, func, NULL);
	}

	for(i=0; i<4; i++)
	{
		pthread_join(threadId[i],NULL);
		printf("join threadId=%lx\n",threadId[i]);
	}
	printf("g_Count=%d\n",g_Count);
	
	sem_destroy(&g_sem);
	
	return 0;
}

运行结果如下,从结果看,也是发挥了锁住临界区的作用:
在这里插入图片描述


✨3.2 信号量在“生产者-消费者”模式的使用

下面代码是信号量在“生产者-消费者”模式的使用,一些线程等待信号量,在另一些线程发布信号量。代码是参考上篇文章介绍条件变量的示例代码修改的,感兴趣的去可以看看。

代码里也有使用到互斥量,因为存在多个线程访问共享资源的情况,虽然也可以使用另一个信号量来做互斥,但那样的代码看起来就很困难。

// 10_producer_consumer_sem.c
// gcc 10_producer_consumer_sem.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#include "linux_list.h"

#define  COMSUMER_NUM	2

typedef struct _product
{
	struct list_head list_node;
	int product_id;
}product_t;

struct list_head productList;// 头结点
pthread_mutex_t product_mutex = PTHREAD_MUTEX_INITIALIZER;	// productList 的互斥量
sem_t 			g_sem;

// 生产者线程,1秒生成一个产品放到链表
void *th_producer(void *arg)
{
	int id = 0;
	while(1)
	{
		product_t *pProduct = (product_t*)malloc(sizeof(product_t));
		pProduct->product_id = id++;
		
		pthread_mutex_lock(&product_mutex);
		list_add_tail(&pProduct->list_node, &productList);
		pthread_mutex_unlock(&product_mutex);
		sem_post(&g_sem);
		
		sleep(1);
	}
	
	return NULL;
}

// 消费者线程,1秒消耗掉一个产品
void *th_consumer(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&product_mutex);
		while(list_empty(&productList)) // 条件不满足
		{
			pthread_mutex_unlock(&product_mutex);
			sem_wait(&g_sem);
			pthread_mutex_lock(&product_mutex);
		}
		// 不为空,则取出一个
		product_t* pProduct = list_entry(productList.next, product_t, list_node);// 获取第一个节点
		printf("consumer[%d] get product id=%d\n", *((int*)arg), pProduct->product_id);
		list_del(productList.next); // 删除第一个节点
		free(pProduct);
		pthread_mutex_unlock(&product_mutex);
	}
	return NULL;
}

int main()
{
	INIT_LIST_HEAD(&productList);	// 初始化链表
	sem_init(&g_sem, 0, 1);			// 初始化信号量
	
	// 创建生产者线程
	pthread_t producer_thid;
	pthread_create(&producer_thid, NULL, th_producer, NULL);
	
	// 创建消费者线程
	pthread_t consumer_thid[COMSUMER_NUM];
	int i=0, num[COMSUMER_NUM]={0,};
	for(i=0; i<COMSUMER_NUM; i++)
	{
		num[i] = i;
		pthread_create(&consumer_thid[i], NULL, th_consumer, &num[i]);
	}
	
	// 等待线程
	pthread_join(producer_thid, NULL);
	for(i=0; i<COMSUMER_NUM; i++)
	{
		pthread_join(consumer_thid[i], NULL);
	}
	
	sem_destroy(&g_sem);
	return 0;
}

运行结果如下,使生产者线程、消费者线程同步访问资源:
在这里插入图片描述


在这里插入图片描述

🎄四、计数信号量的使用例子

计数信号量是指初始化时信号值大于1的信号量,它可以与多个相同的资源关联,允许多个线程并发的使用多个资源。在某种程度上来说,计数信号量是对互斥量的一个扩展,互斥量是同一时间内只允许一个线程访问共享资源,而计数信号量允许多个线程并发访问共享资源。

可以用下面这个例子来加深理解:
1、互斥量相当于只有一个洗手间和一把钥匙,要想进入这个洗手间就要先拿到钥匙,进入洗手间,使用完又把钥匙放回去。
2、计数信号量相当于公共卫生间里的4个厕所和4把钥匙,要想进入厕所就先看看还有几把钥匙,如果没钥匙了就等待,有钥匙放出来就拿钥匙开锁进入洗手间。

下面以上厕所为例,举个计数信号量的例子,8个线程准备使用4个厕所资源,每个线程上两次厕所:

// 10_sem_multiple.c
// gcc 10_sem_multiple.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>

#define  TOILET_NUM		4
#define  PEOPLE_NUM 	8

int 			toilets[TOILET_NUM] = {0,};		// 4个蹲厕
pthread_mutex_t toilet_mutex = PTHREAD_MUTEX_INITIALIZER;	// toilets 的互斥量
sem_t 			g_sem;

int getToilet()
{
	int i=0;
	for(i=0; i<TOILET_NUM; i++)
	{
		if(toilets[i] == 0)
			break;
	}
	return i;
}

int sem_value()
{
	int semvalue = 0;
	sem_getvalue(&g_sem, &semvalue);
	return semvalue;
}

// 上厕所线程
void *going_to_the_toilet(void *arg)
{
	int id = *((int*)arg);
	int count = 2;
	while(count-->0){
		printf("线程[%d] 等待厕所,厕所数量=%d\n",id, sem_value());
		sem_wait(&g_sem);
		pthread_mutex_lock(&toilet_mutex);	// 厕所有多个线程访问,加锁
		int i = getToilet();
		if(getToilet()==TOILET_NUM){
			printf("线程[%d], No toilet\n",id);
		}
		else{
			toilets[i] = 1;		// 表示进入该厕所
			printf("线程[%d] 进入厕所[%d], 即将工作 2s\n",id, i);
			pthread_mutex_unlock(&toilet_mutex); // 上厕所前先释放锁,让其他人可以访问厕所资源
			sleep(2);		// 正在上厕所...
			pthread_mutex_lock(&toilet_mutex);
			toilets[i] = 0;
			printf("线程[%d] 完成工作,厕所[%d]空闲\n",id, i);
		}
		pthread_mutex_unlock(&toilet_mutex);
		sem_post(&g_sem);
		sleep(1);	// 释放资源后,休眠1秒,确保资源让出去
	}
	return NULL;
}

int main()
{
	sem_init(&g_sem, 0, TOILET_NUM);// 初始化信号量值为4
	
	// 创建线程
	pthread_t people_thid[PEOPLE_NUM];
	int i=0, num[PEOPLE_NUM]={0,};
	for(i=0; i<PEOPLE_NUM; i++)
	{
		num[i] = i;
		pthread_create(&people_thid[i], NULL, going_to_the_toilet, &num[i]);
	}
	
	// 等待线程
	for(i=0; i<PEOPLE_NUM; i++)
	{
		pthread_join(people_thid[i], NULL);
	}
	
	sem_destroy(&g_sem);
	return 0;
}

运行结果如下:
在这里插入图片描述


在这里插入图片描述

🎄五、总结

👉本文介绍了信号量的一些基础知识,然后描述了在多线程编程下使用无名信号量的几个场景,并给出了使用例子。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

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

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

相关文章

先撸清楚:并发/并行、单线程/多线程、同步/异步

前言 在编码的过程中经常会遇到并发/并行、同步/异步、单线程/多线程等术语&#xff0c;在分析JS setTimeout/Promise之前先把这些概念厘清。 通过本篇文章&#xff0c;你将了解&#xff1a; 并发/并行的概念及区别同步/异步的概念及区别单线程/多线程的概念及区别主线程和子线…

Next App Router(中)

1.定义布局 布局是指多个页面共享的 UI。在导航的时候&#xff0c;布局会保留状态、保持可交互性并且不会重新渲染&#xff0c;比如用来实现后台管理系统的侧边导航栏。 定义一个布局&#xff0c;你需要新建一个名为 layout.js的文件&#xff0c;该文件默认导出一个 React 组…

XUbuntu18.04 源码编译Qt4.5.3的过程

由于新公司很多旧的软件都是基于这个版本做的嵌入式开发。 所以想要自己搭一套基于Linux的非嵌入式开发环境&#xff0c;方便用来调试和编译代码。 这样就可以完成在linux下开发&#xff0c;然后直接嵌入式打包&#xff0c;涉及到界面的部分就不需要上机调试看问题了。 所以…

一个完全用rust写的开源操作系统-Starry

1. Starry Starry是2023年全国大学生计算机系统能力大赛操作系统设计赛-内核实现赛的二等奖作品。Starry是在组件化OS的arceos的基础上&#xff0c;进行二次开发的操作系统内核&#xff0c;使用宏内核架构&#xff0c;能够运行Linux应用的内核。 原始的操作系统大赛的仓库为 …

linux 基础命令docker及防火墙iptables详解

应用场景&#xff1a; web应用自动打包和发布 自动化测试&#xff0c;持续集成、发布 在服务环境中部署后台应用 搭建paaS平台 安装应用 apt install docker.io#kali中 配置docker源&#xff0c;文件位置/etc/docker/daemon.json { "registry-mirrors": [ "h…

原牛角源码(修罗bbs)全站程序打包带数据库备份

原牛角源码(修罗bbs)全站程序打包带数据库备份&#xff0c;牛角源码全站数据全站文件、插件打包分享给大家&#xff0c;有兴趣的可以搭建玩玩&#xff01; conf文件夹中自己配置conf.php里面的数据库链接文件&#xff0c;默认管理账号&#xff1a;admin&#xff0c;密码&#…

【大数据】bigtable,分布式数据库的鼻祖

目录 1.概述 2.数据模型 3.API 4.架构 5.一个完整的读写过程 6.如何查找到要的tablet 7.LSM树 1.概述 本文是作者阅读完bigtable论文后对bigtable进行的一个梳理&#xff0c;只涉及核心概念不涉及具体实操&#xff0c;具体实操会在后续的文章中推出。 GFS的出现虽然解…

上位机图像处理和嵌入式模块部署(树莓派4b实现xmlrpc通信)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面&#xff0c;我们也用纯API实现过上位机和开发板之间的通信。当时使用的方法&#xff0c;就是用windows自带的网络sdk和linux自带的api函数来完…

分享4张亚马逊云科技AWS免费云开发和AI证书(有答案)

今天给大家带来特别福利&#xff0c;一口气带来亚马逊云科技AWS4张免费云开发/AI证书(有Credly徽章&#xff0c;有答案)&#xff0c;这四门都是云开发相关的硬核知识&#xff0c;含金量极高。 主要考察如何用AWS AI服务进行开发、以及当下热门的云原生改造&#xff0c;16道题80…

葡萄书--关系图卷积神经网络

异质图和知识图谱 同质图与异质图 同质图指的是图中的节点类型和关系类型都仅有一种 异质图是指图中的节点类型或关系类型多于一种 知识图谱 知识图谱包含实体和实体之间的关系&#xff0c;并以三元组的形式存储&#xff08;<头实体, 关系, 尾实体>&#xff0c;即异…

IP地址定位:揭秘精准定位的技术与应用

在数字化时代&#xff0c;IP地址已成为连接互联网世界的关键标识之一。但是&#xff0c;很多人对于IP地址的精准定位能力存在疑虑。本文将深入探讨IP地址定位的技术原理以及其在实际应用中的精确度。 IP地址查询&#xff1a;IP数据云 - 免费IP地址查询 - 全球IP地址定位平台 …

Python中的设计模式与最佳实践

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python中的设计模式与最佳实践 在软件开发中&#xff0c;设计模式是一种解决常见问题的经过…

怎么把网页上的文字变小?

以下是针对常见浏览器的说明&#xff1a; ### Google Chrome&#xff1a; 1. 打开 Chrome 浏览器并导航到您想要调整文字大小的网页。 2. 在页面上右键单击空白处&#xff0c;然后选择 "检查" 或按下 CtrlShiftI&#xff08;在 Windows 或 Linux 上&#xff09;或 Co…

【剪映专业版】14为视频添加炫酷特效

视频课程&#xff1a;B站有知公开课【剪映电脑版教程】 1.特效 画面特效&#xff1a;用于整个画面 人物特效&#xff1a;仅用于画面中的人物&#xff0c;如画面中无人&#xff0c;则不起作用 2.添加特效 按号添加 可通过鼠标推动实现特效时间调节 可叠加使用特效 3.特效修…

关于ERA5气压和温度垂直补偿公式的对比情况

1. 气压和温度垂直补偿对比 「谨代表给个人观点&#xff0c;杠精请自测&#xff0c;对对对&#xff0c;好好好&#xff0c;你说啥都对」。 使用2020-2022陆态网GNSS与探空站并址的48个站点实验&#xff0c;以探空站为真值&#xff0c;验证ERA5精度。怎么确定并址请看前面文章…

平衡二叉树(后序遍历,力扣110)

解题思路&#xff1a;采取后序遍历的好处是先遍历节点得到高度&#xff0c;然后再判断高度差是否大于一&#xff0c;如果是的话就返回-1&#xff0c;不是就返回两高度中较大的高度加一就是父节点的高度 具体代码如下&#xff1a; class Solution { public: int travel(TreeN…

Numpy重修系列(一) --- 初识Numpy

一、为什么使用Numpy&#xff1f; 1.1、简介 Python科学计算基础包&#xff0c;提供 多维数组对象 、派生对象&#xff08;掩码数组、矩阵&#xff09; 数组的快速操作&#xff08;数学计算、逻辑、形状变化、排序、选择、输入输出、离散傅里叶变换、基本线性代数、基本统计运…

利用Python进行大规模数据处理

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 利用Python进行大规模数据处理&#xff1a;Hadoop与Spark的对比 随着数据量的不断增长&…

Pytorch 学习路程

目录 下载Pytorch 入门尝试 几种常见的Tensor Scalar Vector Matrix AutoGrad机制 线性回归尝试 使用hub模块 Pytorch是重要的人工智能深度学习框架。既然已经点进来&#xff0c;我们就详细的介绍一下啥是Pytorch PyTorch 希望将其代替 Numpy 来利用 GPUs 的威力&…

23年新算法,SAO-SVM,基于SAO雪消融算法优化SVM支持向量机回归预测(多输入单输出)-附代码

SAO-SVM是一种基于SAO雪消融算法优化的支持向量机&#xff08;SVM&#xff09;回归预测方法&#xff0c;适用于多输入单输出的情况。下面是一个简要的概述&#xff0c;包括如何使用SAO-SVM进行回归预测的步骤&#xff1a; 步骤&#xff1a; 1. 数据准备&#xff1a; 收集并准…