Linux应用编程(线程同步)(互斥锁)

news2025/1/21 8:59:15

对于一个单线程进程来说,它不需要处理线程同步的问题,所以线程同步是在多线程环境下可能需要注意的一个问题。线程的主要优势在于,资源的共享性,譬如通过全局变量来实现信息共享,不过这种便捷的共享是有代价的,那就是多个线程并发访问共享数据所导致的数据不一致的问题。

一、为什么需要线程同步?

线程同步是为了对共享资源的访问进行保护。
保护的目的是为了解决数据一致性的问题。
出现数据一致性问题其本质在于进程中的多个线程对共享资源的并发访问(同时访问)。

如下程序:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

static int g_count = 0;

static void *new_thread_start(void *arg)
{
	 int loops = *((int *)arg);
	 int l_count, j;
	 for (j = 0; j < loops; j++) 
	 {
		 l_count = g_count;
		 l_count++;
		 g_count = l_count;
	 }
	 return (void *)0;
}

static int loops;

int main(int argc, char *argv[])
{
	 pthread_t tid1, tid2;
	 int ret;
	 /* 获取用户传递的参数 */
	 if (2 > argc)
		 loops = 10000000; //没有传递参数默认为 1000 万次
	 else
		 loops = atoi(argv[1]);
	 /* 创建 2 个新线程 */
	 ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 
	 /* 等待线程结束 */
	 ret = pthread_join(tid1, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_join(tid2, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 /* 打印结果 */
	 printf("g_count = %d\n", g_count);
	 exit(0);
}	 

编译代码,进行测试,首先执行代码,传入参数 1000,也就是让每个线程对全局变量 g_count 递增 1000次,如下所示:
在这里插入图片描述
都打印结果看,得到了我们想象中的结果,每个线程递增 1000 次,最后的数值就是 2000;接着我们把递增次数加大,采用默认值 1000 万次,如下所示:
在这里插入图片描述可以发结果竟然不是我们想看到的样子,执行到最后,应该是 2000 万才对。
为了解决上图中数据不一致的问题,就得需要 Linux 提供的一些方法,也就是接下来将要向大家介绍的线程同步技术

从下图中可知,线程 A 和线程 B 都不会同时访问这个变量,当线程 A 需要修改变量的值时,必须等到写操作完成之后(不能打断它的操作),才运行线程 B 去读取。
在这里插入图片描述
线程的主要优势在于,资源的共享性,譬如通过全局变量来实现信息共享。不过这种便捷的共享是有代价的,必须确保多个线程不会同时修改同一变量、或者某一线程不会读取正由其它线程修改的变量,也就是必须确保不会出现对共享资源的并发访问。Linux 系统提供了多种用于实现线程同步的机制,常见的方法有:互斥锁、条件变量、自旋锁以及读写锁等,下面将向大家一一进行介绍。

二、互斥锁

互斥锁(mutex)又叫互斥量,从本质上说是一把锁,在访问共享资源之前对互斥锁进行上锁,在访问完成后释放互斥锁(解锁);对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻塞,直到当前线程释放互斥锁。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒,它们都会尝试对互斥锁进行加锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能再次陷入阻塞,等待下一次解锁。

1、互斥锁初始化

1.1、使用 PTHREAD_MUTEX_INITIALIZER 宏初始化互斥锁

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

PTHREAD_MUTEX_INITIALIZER 宏已经携带了互斥锁的默认属性。

2、使用 pthread_mutex_init()函数初始化互斥锁

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

函数参数和返回值含义如下:
mutex:参数 mutex 是一个 pthread_mutex_t 类型指针,指向需要进行初始化操作的互斥锁对象;
attr:若将参数 attr 设置为 NULL,则表示将互斥锁的属性设置为默认值,在这种情况下其实就等价于 PTHREAD_MUTEX_INITIALIZER 这种方式初始化,而不同之处在于,使用宏不进行错误检查。
返回值:成功返回 0;失败将返回一个非 0 的错误码。

使用 pthread_mutex_init()函数对互斥锁进行初始化示例:

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

或者:

pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(mutex, NULL);

2.1、互斥锁加锁和解锁

调用函数 pthread_mutex_lock()可以对互斥锁加锁、获取互斥锁,而调用函数 pthread_mutex_unlock()可以对互斥锁解锁、释放互斥锁。其函数原型如下所示:

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

调用 pthread_mutex_unlock()函数将已经处于锁定状态的互斥锁进行解锁。以下行为均属错误:
⚫ 对处于未锁定状态的互斥锁进行解锁操作;
⚫ 解锁由其它线程锁定的互斥锁。

使用示例
使用了一个互斥锁来保护对全局变量 g_count 的访问。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

static pthread_mutex_t mutex;
static int g_count = 0;

static void *new_thread_start(void *arg)
{
	 int loops = *((int *)arg);
	 int l_count, j;
	 
	 for (j = 0; j < loops; j++) 
	 {
		 pthread_mutex_lock(&mutex); //互斥锁上锁
		 l_count = g_count;
		 l_count++;
		 g_count = l_count;
		 pthread_mutex_unlock(&mutex);//互斥锁解锁
	 }
	 return (void *)0;
}

static int loops;

int main(int argc, char *argv[])
{
	 pthread_t tid1, tid2;
	 int ret;
	 /* 获取用户传递的参数 */
	 if (2 > argc)
		 loops = 10000000; //没有传递参数默认为 1000 万次
	 else
		 loops = atoi(argv[1]);
	 /* 初始化互斥锁 */
	 pthread_mutex_init(&mutex, NULL);
	 /* 创建 2 个新线程 */
	 ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 /* 等待线程结束 */
	 ret = pthread_join(tid1, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_join(tid2, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 /* 打印结果 */
	 printf("g_count = %d\n", g_count);
	 exit(0);
}

在这里插入图片描述
可以看到确实得到了我们想看到的正确结果,每次对 g_count 的累加总是能够保持正确,但是在运行程序的过程中,明显会感觉到锁消耗的时间会比较长,这就涉及到性能的问题了,后续会介绍!

2.2、pthread_mutex_trylock()函数

当互斥锁已经被其它线程锁住时,调用 pthread_mutex_lock()函数会被阻塞,直到互斥锁解锁;如果线程不希望被阻塞,可以使用 pthread_mutex_trylock()函数;调用 pthread_mutex_trylock()函数尝试对互斥锁进行加锁,如果互斥锁处于未锁住状态,那么调用 pthread_mutex_trylock()将会锁住互斥锁并立马返回,如果互斥锁已经被其它线程锁住,调用 pthread_mutex_trylock()加锁失败,但不会阻塞,而是返回错误码 EBUSY。

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数 mutex 指向目标互斥锁,成功返回 0,失败返回一个非 0 值的错误码,如果目标互斥锁已经被其它线程锁住,则调用失败返回 EBUSY。

使用示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

static pthread_mutex_t mutex;
static int g_count = 0;

static void *new_thread_start(void *arg)
{
	 int loops = *((int *)arg);
	 int l_count, j;
	 for (j = 0; j < loops; j++) 
	 {
		 while(pthread_mutex_trylock(&mutex)); //以非阻塞方式上锁
		 l_count = g_count;
		 l_count++;
		 g_count = l_count;
		 pthread_mutex_unlock(&mutex);//互斥锁解锁
	 }
 	 return (void *)0;
}

static int loops;

int main(int argc, char *argv[])
{
	 pthread_t tid1, tid2;
	 int ret;
	 /* 获取用户传递的参数 */
	 if (2 > argc)
	 	loops = 10000000; //没有传递参数默认为 1000 万次
	 else
		 loops = atoi(argv[1]);
	 /* 初始化互斥锁 */
	 pthread_mutex_init(&mutex, NULL);
	 /* 创建 2 个新线程 */
	 ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 /* 等待线程结束 */
	 ret = pthread_join(tid1, NULL);
	 if (ret) 
	 {	
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_join(tid2, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 /* 打印结果 */
	 printf("g_count = %d\n", g_count);
	 exit(0);
}	  

整个执行结果跟使用 pthread_mutex_lock()效果是一样的。

2.3、销毁互斥锁

当不再需要互斥锁时,应该将其销毁,通过调用 pthread_mutex_destroy()函数来销毁互斥锁,其函数原型如下所示:

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);

在调用成功情况下返回 0,失败返回一个非 0 值的错误码。
⚫ 不能销毁还没有解锁的互斥锁,否则将会出现错误;
⚫ 没有初始化的互斥锁也不能销毁。
被 pthread_mutex_destroy()销毁之后的互斥锁,就不能再对它进行上锁和解锁了,需要再次调用pthread_mutex_init()对互斥锁进行初始化之后才能使用。

使用示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

static pthread_mutex_t mutex;
static int g_count = 0;

static void *new_thread_start(void *arg)
{
	 int loops = *((int *)arg);
	 int l_count, j;
	 for (j = 0; j < loops; j++) 
	 {
		 pthread_mutex_lock(&mutex); //互斥锁上锁
		 l_count = g_count;
		 l_count++;
		 g_count = l_count;
		 pthread_mutex_unlock(&mutex);//互斥锁解锁
	 }
	 return (void *)0;
}

static int loops;

int main(int argc, char *argv[])
{
	 pthread_t tid1, tid2;
	 int ret;
	 /* 获取用户传递的参数 */
	 if (2 > argc)
		 loops = 10000000; //没有传递参数默认为 1000 万次
	 else
		 loops = atoi(argv[1]);
	 /* 初始化互斥锁 */
	 pthread_mutex_init(&mutex, NULL);
	 /* 创建 2 个新线程 */
	 ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
		 exit(-1);
	 }	
	  /* 等待线程结束 */
	 ret = pthread_join(tid1, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 ret = pthread_join(tid2, NULL);
	 if (ret) 
	 {
		 fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
		 exit(-1);
	 }
	 /* 打印结果 */
	 printf("g_count = %d\n", g_count);
	 /* 销毁互斥锁 */
	 pthread_mutex_destroy(&mutex);
	 exit(0);
}

2.4、互斥锁死锁

试想一下,如果一个线程试图对同一个互斥锁加锁两次,会出现什么情况?情况就是该线程会陷入死锁状态,一直被阻塞永远出不来;这就是出现死锁的一种情况,除此之外,使用互斥锁还有其它很多种方式也能产生死锁。

如下示例代码中所示:

// 线程 A
pthread_mutex_lock(mutex1);
pthread_mutex_lock(mutex2);
// 线程 B
pthread_mutex_lock(mutex2);
pthread_mutex_lock(mutex1);

2.5、互斥锁的属性
当对象不再使用时,需要使用 pthread_mutexattr_destroy()将其销毁,函数原型如下所示:

#include <pthread.h>
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);

参数 attr 指向需要进行初始化的 pthread_mutexattr_t 对象,调用成功返回 0,失败将返回非 0 值的错误码。
互斥锁的类型属性控制着互斥锁的锁定特性,一共有 4 中类型:

⚫ PTHREAD_MUTEX_NORMAL:一种标准的互斥锁类型,不做任何的错误检查或死锁检测。如果线程试图对已经由自己锁定的互斥锁再次进行加锁,则发生死锁;互斥锁处于未锁定状态,或者已由其它线程锁定,对其解锁会导致不确定结果。
⚫ PTHREAD_MUTEX_ERRORCHECK:此类互斥锁会提供错误检查。譬如这三种情况都会导致返回错误:线程试图对已经由自己锁定的互斥锁再次进行加锁(同一线程对同一互斥锁加锁两次),返回错误;线程对由其它线程锁定的互斥锁进行解锁,返回错误;线程对处于未锁定状态的互斥锁进行解锁,返回错误。这类互斥锁运行起来比较慢,因为它需要做错误检查,不过可将其作为调试工具,以发现程序哪里违反了互斥锁使用的基本原则。
⚫ PTHREAD_MUTEX_RECURSIVE:此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁,但是如果解锁次数不等于加速次数,则是不会释放锁的;所以,如果对一个递归互斥锁加锁两次,然后解锁一次,那么这个互斥锁依然处于锁定状态,对它再次进行解锁之前不会释放该锁。
⚫ PTHREAD_MUTEX_DEFAULT : 此 类 互 斥 锁 提 供 默 认 的 行 为 和 特 性 。 使 用 宏PTHREAD_MUTEX_INITIALIZER 初 始 化 的 互 斥 锁 , 或 者 调 用 参 数 arg 为 NULL 的pthread_mutexattr_init()函数所创建的互斥锁,都属于此类型。此类锁意在为互斥锁的实现保留最大灵活性, Linux 上 , PTHREAD_MUTEX_DEFAULT 类 型 互 斥 锁 的 行 为 与PTHREAD_MUTEX_NORMAL 类型相仿。

可以使用 pthread_mutexattr_gettype()函数得到互斥锁的类型属性,使用 pthread_mutexattr_settype()修改

/设置互斥锁类型属性,其函数原型如下所示:
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

而对于 pthread_mutexattr_settype()函数,会将参数 attr 指向的 pthread_mutexattr_t 对象的类型属性设置为参数 type 指定的类型。使用方式如下:

pthread_mutex_t mutex;
pthread_mutexattr_t attr;
/* 初始化互斥锁属性对象 */
pthread_mutexattr_init(&attr);
/* 将类型属性设置为 PTHREAD_MUTEX_NORMAL */
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
/* 初始化互斥锁 */
pthread_mutex_init(&mutex, &attr);
......
/* 使用完之后 */
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&mutex);

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

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

相关文章

docker网桥冲突解决方法

Docker网桥网段冲突导致访问不到容器问题 三种情况 一、docker0所用网段与局域网所用网段相同&#xff0c;导致网桥冲突&#xff0c;这会造成冲突网段无法访问docker服务。 解决办法&#xff1a; 1.停止docker&#xff0c;删除冲突网桥 systemctl stop docker ip link del doc…

干货 | 中科院心理所考研复试经验分享

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐&#xff5e; 此时此刻&#xff0c;23年考研的小伙伴估计正在为复试进行准备吧&#xff0c;大家都准备得怎么样了呢&#xff1f; 今天为大家带来的就是我国顶级心理学研究结构—中科院心理所…

leetcode19_删除链表的倒数第 N 个结点

文章目录 题目详情分析Java代码实现 题目详情 leetcode19 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 分析 暴力的方法&#xff0c;两趟遍历&#xff0c;第一个遍历记录总的节点数目&#xff0c;第二次遍历到总结点数-N个位置&…

PyTorch——利用Accelerate轻松控制多个CPU/GPU/TPU加速计算

PyTorch——利用Accelerate轻松控制多个CPU/GPU/TPU加速计算 前言官方示例控制多个CPU/GPU/TPU简单说一下设备环境导包加载数据 FashionMNIST创建一个简单的CNN模型训练函数-只包含训练训练函数-包含训练和验证训练 参考链接 前言 CPU&#xff1f;GPU&#xff1f;TPU&#xff…

边学边记——数据结构☞关于对象的比较(包括对equals()方法的重写,Comparable接口,Comparator接口的介绍)

目录 一、基本类型 二、引用类型——对象的比较 1. 关于同一性的比较 2. 关于相等性的比较 三、Comparable接口 1. 介绍 2. 实现 3. 什么叫做正确的compareTo方法的重写 四、实现Comparator接口&#xff08;基于比较器比较&#xff09; 1. 介绍 2. 实现 3. 使用场景…

CentOS防火墙的常用快捷命令

CentOS是免费开源的Linux发行版之一,它兼容RHEL并由社区进行维护,大多数美国服务器提供对该系统支持。在使用CentOS系统时,您需要了解一些常用命令,比如开启、查看、关闭防火墙等。本文将介绍下CentOS防火墙的常用命令。 CentOS是一种面向企业级服务器环境的Linux发行版,…

D触发器仿真实验

关于D触发器的内容见专栏的单片机原理及应用&#xff0c;主要是时钟脉冲出现时候&#xff0c;会改变输出状态。 下面来做一个D触发器的仿真实验。 部件 使用74LS74&#xff1a;带清除和预置端功能的双上升沿D型触发器 74LS74是一款现代集成电路芯片&#xff0c;属于TTL&…

火爆全网,测试用例技巧-Python实现XMind测试用例转Excel测试用例

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 自动化测试&#x…

Jenkins中Changelog插件使用

Jenkins中Changelog插件使用 通过changlog插件获取每次提交的变更信息 一、安装changelog插件 二、使用changelog 1、新建项目&#xff0c;在构建环境中勾选Add Changelog Information to Environment &#xff0c;输入对应的格式 %3$s(datetime-at%4$s via%1$s) %2$s(type%…

聚焦智慧燃气,美格智能亮相第25届中国国际燃气、供热技术与设备展览会

4月25~27日&#xff0c;第25届中国国际燃气、供热技术与设备展览会在深圳会展中心&#xff08;福田&#xff09;盛大举办。本次展会由中国城市燃气协会主办&#xff0c;自1994年举办以来已历经24届&#xff0c;是国内燃气行业规模巨大的综合性专业展会。美格智能携4G/5G模组、N…

Typora中的数学公式(MarkDown)

Typora中的数学公式&#xff08;MarkDown&#xff09; 添加数学公式方式&#xff1a; 1.选择&#xff1a;段落>公式 2.快捷键&#xff1a;Ctrl Shift M 3.直接输入两个美元符号并回车 展示&#xff1a; 空格&#xff1a;一个斜线\ : a \ b a b a \ b a b 换行&#x…

【河南省第二届技能大赛-物联网技术】C模块Python开发讲解

文章目录 前言题目如下什么是云平台APIAPI调用的基本流程Python实例介绍依赖库介绍API调用的基本参数请求返回值requests库PyQt5库总结源码链接 前言 这是基于样题进行讲解的文章&#xff0c;因为正式赛题尚未公布和样题类型相似。文章结尾提供了源码和环境链接&#xff0c;读…

四面阿里,成功入职阿里测试开发,分享我的真实面试题

闲话少叙 直接上干货 鉴于篇幅所限&#xff0c;这里不放答案&#xff0c;有需要的朋友可以评论区自取 1. 请自我介绍一下(需简单清楚的表述自已的基本情况&#xff0c;在这过程中要展现出自信&#xff0c;对工作有激情&#xff0c;上进&#xff0c;好学) 2. 平时工作中是怎么去…

【面试题】你都必须得掌握的vue知识

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 前言 大家好&#xff0c;我是前端贰货道士。最近抽空整理了下我对vue2.x的理解和认知&#…

设计师常用的素材网站有哪个推荐

即时设计资源社区聚集了许多优秀的创作者&#xff0c;分享了大量的优质资源。 目前&#xff0c;社区资源数量已达到10000&#xff0c;包含图标、插画、原型、设计作品等多个素材类别。这些优秀的设计作品降低了设计师思维的成本&#xff0c;成为设计师的宝藏材料网站。 即时设…

ML@sklearn@ML流程Part2@数据划分@KFold折叠交叉验证

文章目录 MLsklearnML流程Part2数据划分KFold折叠交叉验证Model evaluation数据划分sklearn中的模型评估demo K-fold cross-validation&#x1f388;K-foldegegeg:KFoldK-fold cross-validation ShuffleSpliteg 小结 Stratified Shuffle Splitegeg demo MLsklearnML流程Part2数…

代码随想录算法训练营第四十四天|完全背包理论基础 、518. 零钱兑换 II 、377. 组合总和 Ⅳ

文章目录 完全背包理论基础518. 零钱兑换 II377. 组合总和 Ⅳ 完全背包理论基础 纯背包问题的特点&#xff1a;每个物品可以无限次拿 与0-1背包唯一不同&#xff1a; 完全背包的物品是可以添加多次的&#xff0c;所以要从小到大去遍历 0-1背包不可以添加多次&#xff0c;需要从…

【C++】类和对象(上篇)

类和对象 面向过程和面向对象初步认识类的引入类的定义命名规范类的访问限定符及封装访问限定符封装 类的作用域类的实例化类的对象大小的计算类成员函数的this指针this指针的引出this指针的特性 面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#…

RocketMQ-Producer

消息生产者的代码都在client模块中&#xff0c;相对于RocketMQ来讲&#xff0c;消息生产者就是客户端&#xff0c;也是消息的提供者。 启动流程 代码&#xff1a;DefaultMQProducerImpl#start public void start(final boolean startFactory) throws MQClientException {switc…

轻松掌握消息队列RabbitMQ在SpringAMQP中的实践知识点

1、介绍 spring集成了rabbitmq&#xff0c;可以对rabbitmq先进行安装简单了解。参考我的rabbitmq文章。 2、使用 1、基本消息队列BasicQueue案例 一个消息消费者&#xff0c;上个消息未处理完&#xff0c;队列中的消息将阻塞&#xff0c;导致内存泄漏 1、引入AMQP依赖 2、添…