【Linux C | 多线程编程】线程同步 | 条件变量(万字详解)
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-04-15 08:58:04
本文未经允许,不得转发!!!
目录
- 🎄一、概述
- 🎄二、为什么需要条件变量
- 🎄三、条件变量相关函数
- ✨3.1 条件变量的初始化
- ✨3.2 条件变量的销毁
- ✨3.3 条件变量的等待
- ✨3.4 条件变量的唤醒
- 🎄四、条件变量的使用
- ✨4.1 条件等待的使用
- ✨4.2 条件唤醒的使用
- ✨4.3 条件变量使用例子
- 🎄五、总结
🎄一、概述
多线程编程中,涉及到线程并发,因此也衍生了一些问题,常见的有2类问题:
- 1、多个线程访问同一个共享资源,这个可以用上篇文章 线程同步|互斥量的介绍和使用 介绍的互斥量来解决;
- 2、某些线程运行后,有时需要等待一定的条件才会继续执行,如果条件不满足就会等待,而条件的达成, 很可能取决于另一个线程。
本文要介绍的条件变量主要是解决上面的第二个问题。条件变量用来阻塞一个线程,直到某条件满足为止。通常条件变量和互斥锁同时使用。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
🎄二、为什么需要条件变量
因为如果不使用条件变量,线程就需要 轮询+休眠 来查看是否满足条件,这样严重影响效率。
下面是不使用条件变量的代码:
1、创建一个线程作为生产者,一秒生成一个产品;
2、创建两个线程作为消费者,分别一秒消耗一个产品,此时消费者线程 不得不每隔一秒就查询是否有数据;
3、需要添加"linux_list.h"头文件,文件在下面会给出,它是Linux内核使用的一个链表。需要了解使用方法的,可以看这文章【数据结构】list.h 详细使用教程
// 09_producer_consumer.c
// gcc 09_producer_consumer.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "linux_list.h"#define COMSUMER_NUM 2typedef 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 的互斥量// 生产者线程,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);sleep(1);}return NULL;
}// 消费者线程,1秒消耗掉一个产品
void *th_consumer(void *arg)
{while(1){pthread_mutex_lock(&product_mutex);if(!list_empty(&productList)) // 不为空,则取出一个{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);sleep(1);}return NULL;
}int main()
{INIT_LIST_HEAD(&productList); // 初始化链表// 创建生产者线程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);}return 0;
}
下面是"linux_list.h"的头文件:
// my_list.h 2023-09-24 23:07:43
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_Hstruct list_head {struct list_head *next, *prev;
};#define INIT_LIST_HEAD(ptr) do { \(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)/*** list_empty - tests whether a list is empty* @head: the list to test.*/
static inline int list_empty(const struct list_head *head)
{return head->next == head;
}/** Insert a new entry between two known consecutive entries.** This is only for internal list manipulation where we know* the prev/next entries already!*/
static inline void __list_add(struct list_head *new_node,struct list_head *prev,struct list_head *next)
{next->prev = new_node;new_node->next = next;new_node->prev = prev;prev->next = new_node;
}/*** list_add - add a new entry* @new: new entry to be added* @head: list head to add it after** Insert a new entry after the specified head.* This is good for implementing stacks.*/
static inline void list_add(struct list_head *new_node, struct list_head *head)
{__list_add(new_node, head, head->next);
}/*** list_add_tail - add a new entry* @new: new entry to be added* @head: list head to add it before** Insert a new entry before the specified head.* This is useful for implementing queues.*/
static inline void list_add_tail(struct list_head *new_node, struct list_head *head)
{__list_add(new_node, head->prev, head);
}/** Delete a list entry by making the prev/next entries* point to each other.** This is only for internal list manipulation where we know* the prev/next entries already!*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{next->prev = prev;prev->next = next;
}/*** list_del - deletes entry from list.* @entry: the element to delete from the list.* Note: list_empty on entry does not return true after this, the entry is* in an undefined state.*/
static inline void list_del(struct list_head *entry)
{__list_del(entry->prev, entry->next);//entry->next = (struct list_head *)LIST_POISON1;//entry->prev = (struct list_head *)LIST_POISON2;
}#ifndef offsetof
#define offsetof(type, f) ((size_t) \((char *)&((type *)0)->f - (char *)(type *)0))
#endif#ifndef container_of
#define container_of(ptr, type, member) ({ \const typeof( ((type *)0)->member ) *__mptr = (ptr);\(type *)( (char *)__mptr - offsetof(type,member) );})
#endif/*** list_entry - get the struct for this entry* @ptr: the &struct list_head pointer.* @type: the type of the struct this is embedded in.* @member: the name of the list_struct within the struct.*/
#define list_entry(ptr, type, member) \container_of(ptr, type, member)#ifndef ARCH_HAS_PREFETCH
static inline void prefetch(const void *x) {;}
#endif/*** list_for_each - iterate over a list* @pos: the &struct list_head to use as a loop counter.* @head: the head for your list.*/
#define list_for_each(pos, head) \for (pos = (head)->next; prefetch(pos->next), pos != (head); \pos = pos->next)/*** list_for_each_safe - iterate over a list safe against removal of list entry* @pos: the &struct list_head to use as a loop counter.* @n: another &struct list_head to use as temporary storage* @head: the head for your list.*/
#define list_for_each_safe(pos, n, head) \for (pos = (head)->next, n = pos->next; pos != (head); \pos = n, n = pos->next)/*** list_for_each_entry - iterate over list of given type* @pos: the type * to use as a loop counter.* @head: the head for your list.* @member: the name of the list_struct within the struct.*/
#define list_for_each_entry(pos, head, member) \for (pos = list_entry((head)->next, typeof(*pos), member); \prefetch(pos->member.next), &pos->member != (head); \pos = list_entry(pos->member.next, typeof(*pos), member))#endif //_LINUX_LIST_H
🎄三、条件变量相关函数
这小节先介绍条件变量的相关函数接口,条件变量都是要结合互斥量一起使用的,具体用法在下个小节介绍。
✨3.1 条件变量的初始化
POSIX提供了两种初始化条件变量的方法。
-
1、是将
PTHREAD_COND_INITIALIZER
赋值给定义的条件变量,如下:#include <pthread.h> pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
但这个方法没办法设置条件变量的属性,也不适用于动态分配的条件变量。
-
2、使用
pthread_cond_init
初始化条件变量。如下:#include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
第二个
pthread_condattr_t
指针的入参,是用来设定条件变量的属性的。大部分情况下,并不需要设置条件变量的属性,传递NULL即可,表示使用条件变量的默认属性。
注意:永远不要用一个条件变量对另一个条件变量赋值, 即pthread_cond_t cond_b=cond_a
不合法, 这种行为是未定义的。
✨3.2 条件变量的销毁
使用pthread_cond_init
初始化的条件变量,在确定不再使用的时候, 就要销毁它。 在销毁之前, 有三点需要注意:
1、使用PTHREAD_COND_INITIALIZE
静态初始化的条件变量,不需要被销毁。
2、要调用pthread_cond_destroy
销毁的条件变量可以调用pthread_cond_init
重新进行初始化。
3、不要引用已经销毁的条件变量, 这种行为是未定义的。
销毁条件变量的接口如下:
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
✨3.3 条件变量的等待
条件变量就是为了与某个条件关联起来使用的,如果条件不满足,就等待(pthread_cond_wait
) ,或者等待一段有限的时间(pthread_cond_timedwait
) 。POSIX提供了如下条件变量的等待接口:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
函数描述:这两个函数都是让指定的条件变量进入等待状态,其工作机制是先解锁传入的互斥量,再让条件变量等待,从而使所在线程处于阻塞状态。这两个函数返回时,系统会确保该线程再次持有互斥量(加锁)。
两个函数的区别:pthread_cond_wait
函数调用成功后,会一直阻塞等待,直到条件变量被唤醒。而 pthread_cond_timedwait
函数只会等待指定的时间,时间到了之后,条件变量仍未被唤醒的话,会返回一个错误码ETIMEDOUT
,该错误码定义在<errno.h>
头文件。
注意:pthread_cond_timedwait
指定的时间是绝对时间,而不是相对时间。如果最多等待2分钟,那么这个值应该是当前时间加上2分钟。使用方法可以参考下面代码
struct timeval now;
struct timespec outtime;
memset(&outtime,0,sizeof(outtime));
memset(&now,0,sizeof(now));gettimeofday(&now, NULL);
outtime.tv_sec = now.tv_sec + sec;
outtime.tv_nsec = now.tv_usec * 1000;// pthread_cond_timedwait 执行后,先加锁pMutex、再等待;被唤醒后会加锁pMutex
if(pthread_cond_timedwait(pCond, pMutex, &outtime) == ETIMEDOUT)
{ret = -1;
}
✨3.4 条件变量的唤醒
上面说完了条件等待,接下来介绍条件变量的唤醒。调用完条件变量等待函数的线程处于阻塞状态,若要被唤醒,必须是其他线程来唤醒。POSIX提供了如下两个接口来唤醒指定的条件变量:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_signal 负责唤醒等待在条件变量上的一个线程,如果有多个线程等待,是唤醒哪一个呢?Linux内核会为每个条件变量维护一个等待队列,调用了 pthread_cond_wait
或 pthread_cond_timedwait
的线程会按照调用时间先后添加到该队列中。pthread_cond_signal会唤醒该队列的第一个。
pthread_cond_broadcast,就是广播唤醒等待在条件变量上的所有线程。前面说过,条件等待的两个函数返回时,系统会确保该线程再次持有互斥量(加锁),所有,这里被唤醒的所有线程都会去争夺互斥锁,没抢到的线程会继续等待,拿到锁后同样会从条件等待函数返回。所以,被唤醒的线程第一件事就是再次判断条件是否满足!
🎄四、条件变量的使用
要清楚条件变量怎样使用?就是要弄清楚条件变量的怎样初始化、销毁、等待、唤醒。通过上个小节的学习,条件变量初始化、销毁应该都可以弄懂,而条件变量的等待、唤醒都使用到互斥量,会显得比较复杂,刚学习时总会不清楚这个互斥量应该锁在哪个位置,也容易被 条件等待 的机制弄晕,这小节就看看写代码时,怎样写条件变量的等待、唤醒。
✨4.1 条件等待的使用
条件变量的等待函数都会用到两个参数,一个是条件变量指针,另一个是互斥量指针。其中,条件变量指针比较好理解,直接把初始化好的条件变量指针传入即可。而互斥量指针参数要求传入一个互斥量地址,那这个互斥量在哪里加锁,哪里解锁呢?
一般情况下,在调用条件等待(pthread_cond_wait
)函数之前,会判断条件是否满足,不满足的话才进入条件等待。而这个条件是需要其他线程来使其满足的,这里就出现了多个线程访问共享资源,为了安全地获取和修改共享数据,就需要一个互斥量来同步。到这里可以得出,访问与条件相关的共享资源时,会有一个互斥量锁住这个访问的临界区。那这个互斥量 和 pthread_cond_wait
需要的互斥量是不是同一个呢?为什么?假设这两个互斥量不是同一个的话,就会出现一个问题,在判断完条件之后,调用pthread_cond_wait
之前,可能条件被修改为满足了,这显然不是我们想要的,我们需要的是“判断条件、不满足就等待”这两个操作直接不会被其他线程干扰到。
所以,使用条件变量等待时,会使用一个互斥量进行加锁,加锁的临界区包含了“①查看是否满足条件、②调用pthread_cond_wait
进入条件等待”这两个操作。
🌰举例子:
1、productList是判断条件需要用到的共享资源;
2、product_mutex 是操作时用于加锁的互斥量 productList;
3、product_cond 是条件变量
pthread_mutex_lock(&product_mutex);
while(list_empty(&productList)) // 条件不满足
{pthread_cond_wait(&product_cond, &product_mutex);
}
// 不为空,则取出一个
pthread_mutex_unlock(&product_mutex);
✨4.2 条件唤醒的使用
上面介绍了怎么等待了,那唤醒等待条件就需要另一个线程在操作,这个线程在唤醒之前肯定是要有一个操作令前面等待的条件满足的,然后才是唤醒(pthread_cond_signal
)。修改条件,肯定也是被互斥量加锁保护的,那唤醒是放在互斥量加锁的临界区内还是临界区外呢?答案是都可以,但放在临界区外,会效率更高。
从前面知道,pthread_cond_wait函数返回时,系统会确保该线程再次持有互斥量(加锁)。也就是说,条件等待的线程被唤醒后会再次对互斥量加锁。知道这点后,再看上面说的两个情况。
唤醒(
pthread_cond_signal
)在临界区内的代码如下,会出现线程被唤醒了,并没能第一时间获取到互斥量,需要等到下面第4行执行后,才获取到互斥量,所以效率低一点。thread_mutex_lock(&product_mutex); list_add_tail(&pProduct->list_node, &productList); // 改变了条件 pthread_cond_signal(&product_cond); pthread_mutex_unlock(&product_mutex);
唤醒(
pthread_cond_signal
)在临界区外的代码如下,因为先解锁了条件变量,所以thread_mutex_lock(&product_mutex); list_add_tail(&pProduct->list_node, &productList); // 改变了条件 pthread_mutex_unlock(&product_mutex); pthread_cond_signal(&product_cond);
✨4.3 条件变量使用例子
下面是第二小节的例子使用了条件变量的代码,可以看到不需要sleep来休眠了。
// 09_producer_consumer_cond.c
// gcc 09_producer_consumer_cond.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include "linux_list.h"#define COMSUMER_NUM 2typedef 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 的互斥量
pthread_cond_t product_cond = PTHREAD_COND_INITIALIZER; // 条件变量// 生产者线程,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_cond_signal(&product_cond);pthread_mutex_unlock(&product_mutex);sleep(1);}return NULL;
}// 消费者线程,1秒消耗掉一个产品
void *th_consumer(void *arg)
{while(1){pthread_mutex_lock(&product_mutex);while(list_empty(&productList)) // 条件不满足{pthread_cond_wait(&product_cond, &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); // 初始化链表// 创建生产者线程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);}return 0;
}
🎄五、总结
本文介绍了Linux多线程编程中常见的条件变量,先是介绍了条件变量相关接口函数,然后介绍了怎么使用条件变量,最后给出了使用条件变量的例子。
学习完上面的内容,看看是否知道下面几个问题的答案:
1、为什么需要条件变量?
2、条件变量为什么需要和互斥量一起使用?
3、pthread_cond_wait函数做了什么操作?
4、既然互斥量和条件变量关系如此紧密,为什么不干脆将互斥量变成条件变量的一部分呢?
5、互斥量加锁的临界区应该包含哪些操作?
6、先唤醒后解锁,还是先解锁后唤醒?
7、为什么条件等待时,使用while来判断条件,而不是用if ?
while(list_empty(&productList)) // 条件不满足
{pthread_cond_wait(&product_cond, &product_mutex);
}
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁
相关文章:

【Linux C | 多线程编程】线程同步 | 条件变量(万字详解)
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 ⏰发布时间⏰:2024-04-15 0…...

【高阶数据结构】哈希表 {哈希函数和哈希冲突;哈希冲突的解决方案:开放地址法,拉链法;红黑树结构 VS 哈希结构}
一、哈希表的概念 顺序结构以及平衡树 顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系。因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N);平衡树中为树的高度,即O(log_2 N)…...

嵌入式之计算机网络篇(七)
七、计算机网络 1.说说计算机网络五层体系结构 计算机网络的五层架构包括应用层、传输层、网络层、数据链路层和物理层。 应用层:是网络结构中的最高层,负责向用户提供网络服务,如文件传输、电子邮件、远程登录等。常见的应用层协议有HTTP…...

C++|运算符重载(1)|为什么要进行运算符重载
写在前面 本篇里面的日期类型加法,先不考虑闰年,平年的天数,每月的天数统一按30天算,那么每一年也就是360天 目录 写在前面 定义 基本数据类型 自定义数据类型 成员函数解决相加问题 Date类+整形 下一篇----运…...

【ARM 裸机】汇编 led 驱动之烧写 bin 文件
1、烧写概念 bin 文件烧写到哪里呢?使用 STM32 的时候烧写到内部 FLASH,6ULL 没有内部 FLASH,是不是就不能烧写呢?不,6ULL 支持 SD卡、EMMC、NAND FLASH、NOR FLASH 等方式启动,在裸机学习的工程中&#x…...

计算机网络之CIDR
快速了解CIDR CIDR 表示的是什么? 单个IP地址:当你看到一个CIDR表示法,如192.168.1.1/32,它表示一个单独的具体IP地址。/32表示所有32位都是网络部分,没有主机部分,因此它指的是单一的IP地址。 一个IP地址…...

【无标题】系统思考—智慧共赢座谈会
第432期JSTO—“智慧共赢座谈会”精彩回顾 我们身处一个快速变化的世界,其中培训和咨询行业也不断面临新的挑战和机遇。为了紧跟这些变革,我们邀请了行业专家与合作伙伴深入探讨在培训、交付和销售过程中遇到的难题。 本次座谈会的亮点之一是我们科学上…...

【Linux C | 多线程编程】线程同步 | 互斥量(互斥锁)介绍和使用
😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 ⏰发布时间⏰: 本文未经允许…...

mid_360建图和定位
录制数据 roslaunch livox_ros_driver2 msg_MID360.launch使用fast-lio 建图 https://github.com/hku-mars/FAST_LIO.git 建图效果 使用python做显示 https://gitee.com/linjiey11/mid360/blob/master/show_pcd.py 使用 point_lio建图 https://github.com/hku-mars/Point…...

ThreadX在STM32上的移植:通用启动文件tx_initialize_low_level.s
在嵌入式系统开发中,实时操作系统(RTOS)的选择对于系统性能和稳定性至关重要。ThreadX是一种广泛使用的RTOS,它以其小巧、快速和可靠而闻名。在本文中,我们将探讨如何将ThreadX移植到STM32微控制器上,特别是…...

【python实战】游戏开发——恐龙跳跃小游戏
目录 背景开发环境步骤初步搭建主窗口的实现地平线滑动场景小恐龙跳跃障碍物出现碰撞检测和积分功能优化改为随时可以跳跃源码参考背景 小恐龙沿着地平线前进,遇到障碍物跳跃,躲避障碍物。根据躲避的障碍物进行积分统计。 开发环境 开发环境:Windows10 软件:pycharm 开发…...

成都百洲文化传媒有限公司电商领域的新锐力量
在电商服务领域,成都百洲文化传媒有限公司凭借其专业的服务理念和创新的策略,正逐渐成为行业内的翘楚。这家公司不仅拥有资深的电商团队,还以其精准的市场定位和高效的服务模式,赢得了众多客户的信赖和好评。 一、专业团队&#…...

1042: 中缀表达式转换为后缀表达式
解法:直接给算法 创建一个栈和一个空的后缀表达式字符串。 遍历中缀表达式中的每个字符。 如果当前字符是操作数,直接将其添加到后缀表达式字符串中。 如果当前字符是操作符,需要将其与栈顶的操作符进行比较: 如果栈为空&#…...

避免上下文切换--Linux原子函数
在现代操作系统中,原子函数是一类特殊的函数,它们能够保证在并发环境中执行的操作是不可分割的。这意味着一旦一个原子函数开始执行,它的操作会在任何其他线程或进程可以介入之前完全完成。这是通过多种机制实现的,包括硬件支持的…...

塔面板php7.37.4版本不支持ZipArchive手工安装扩展方法
centos 7 宝塔面板安装的PHP7.3和7.4默认已经不带zip扩展,要手工安装zip扩展首先需要安装libzip, yum -y install libzip 方法如下: 宝塔面板php7.3版本在SSH命令行界面执行以下语句: cd /www/server/php/73/src/ext/zip/ /ww…...

go语言并发实战——日志收集系统(一) 项目前言
-goroutine- 简介 go并发编程的练手项目 项目背景 一般来说业务系统都有自己的日志,当系统出现问题时,我们一般需要通过日志信息来定位与解决问题,当系统机器较少时我们可以登录服务器来查看,但是当系统机器较多时,我们通过服务器来查看日志的成本就会变得很大,…...

Android Studio 之 Intent及其参数传递
一、Intent 显式Intent:通过组件名指定启动的目标组件,比如startActivity(new Intent(A.this,B.class)); 每次启动的组件只有一个~隐式Intent:不指定组件名,而指定Intent的Action,Data,或Category,当我们启动组件时, 会去匹配AndroidManifest.xml相关组件的Intent-…...

【黑马头条】-day06自媒体文章上下架-Kafka
文章目录 今日内容1 Kafka1.1 消息中间件对比1.2 kafka介绍1.3 kafka安装及配置1.4 kafka案例1.4.1 导入kafka客户端1.4.2 编写生产者消费者1.4.3 启动测试1.4.4 多消费者启动 1.5 kafka分区机制1.5.1 topic剖析 1.6 kafka高可用设计1.7 kafka生产者详解1.7.1 同步发送1.7.2 异…...
非线性特征曲线线性化插补器(CODESYS 完整ST代码)
1、如何利用博途PLC和信捷PLC实现非线性特征曲线的线性化可以参考下面文章链接: 非线性特征曲线线性化(插补功能块SCL源代码+C代码)_scl直线插补程序-CSDN博客文章浏览阅读382次。信捷PLC压力闭环控制应用(C语言完整PD、PID源代码)_RXXW_Dor的博客-CSDN博客闭环控制的系列文章…...

vue3从精通到入门4:diff算法的实现
Vue 3 的 diff 算法相较于 Vue 2 有了一些改进和优化,主要是为了应对更复杂的组件结构和更高的性能需求。 以下是 Vue 3 diff 算法在处理列表更新时的大致步骤: 头头比较:首先,比较新旧列表的头节点(即第一个节点&…...

(三)C++自制植物大战僵尸游戏项目结构说明
植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/ErelL 一、项目结构 打开项目后,在解决方案管理器中有五个项目,分别是libbox2d、libcocos2d、librecast、libSpine、PlantsVsZombies五个项目,除PlantsVsZombies外,其他四个…...

动态规划专练( 279.完全平方数)
279.完全平方数 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 …...

京东商品详情API接口(商品属性丨sku价格丨详情图丨标题等数据)
京东商品详情API接口是京东开放平台提供的一种API接口,通过调用该接口,开发者可以获取京东商品的标题、价格、库存、月销量、总销量、详情描述、图片等详细信息。下面针对您提到的商品属性、SKU价格、详情图以及标题等数据,做具体介绍&#x…...

Springboot+Vue项目-基于Java+MySQL的校园周边美食探索及分享平台系统(附源码+演示视频+LW)
大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…...

折叠面板组件(vue)
代码 <template><div class"collapse-info"><div class"collapse-title"><div class"title-left">{{ title }}</div><div click"changeHide"> <Button size"small" v-if"sho…...

【Canvas技法】蓝底金字北岛诗节选(径向渐变色、文字阴影示例)
【效果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>北岛诗选</title><style type"text/css">.c…...

【大语言模型】基础:TF-IDF
TF-IDF (Term Frequency-Inverse Document Frequency) 是一种用于信息检索与文本挖掘的统计方法,用来评估一个词对于一个文件集或一个语料库中的其中一份文件的重要性。它是一种常用于文本处理和自然语言处理的权重计算技术。 原理 TF-IDF 由两部分组成࿱…...

[开发日志系列]PDF图书在线系统20240415
20240414 Step1: 创建基础vueelment项目框架[耗时: 1h25min(8:45-10:10)] 检查node > 升级至最新 (考虑到时间问题,没有使用npm命令行执行,而是觉得删除重新下载最新版本) > > 配置vue3框架 取名:Online PDF Book System 遇到的报错: 第一报错: npm ERR! …...

蓝桥杯 — — 纯质数
纯质数 题目: 思路: 一个最简单的思路就是枚举出所有的质数,然后再判断这个质数是否是一个纯质数。 枚举出所有的质数: 可以使用常规的暴力求解法,其时间复杂度为( O ( N N ) O(N\sqrt{N}) O(NN )&…...

OpenCV基本图像处理操作(三)——图像轮廓
轮廓 cv2.findContours(img,mode,method) mode:轮廓检索模式 RETR_EXTERNAL :只检索最外面的轮廓;RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;RETR_CCOMP:检索所有的轮廓,并将他们组…...