【Linux】:多线程(读写锁 自旋锁)
✨ 倘若南方知我意,莫将晚霞落黄昏 🌏
📃个人主页:island1314
🔥个人专栏:Linux—登神长阶
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
目录
1. 读写锁 🔏
1.1 基本概念
1.2 读写锁的工作原理及特点
1.3 读写锁的实现
🥝 读写锁相关函数
🥝 案例如下:
1.4 读写锁优缺点及应用场景
🍉 优点:
🍉 缺点:
🍉 读写锁应用场景:
1.5 性能开销:读写锁 VS 互斥锁
2. 自旋锁 🔐
2.1 基本概念
2.2 自旋锁的原理
2.3 自旋锁实现
2.4 自旋锁优缺点及应用场景
🥑 优点:
🥑 缺点:
🥑使用场景:
2.5 自旋锁 VS 互斥锁
1. 读写锁 🔏
1.1 基本概念
🔥 读写锁(Read-Write Lock)是一种用于多线程环境下同步访问共享资源的锁。它与传统的互斥锁(Mutex
)有所不同,提供了更细粒度的控制,以便提高并发性能。它允许多个线程同时 读取 数据,但在写入数据时,必须确保只有一个线程可以进行写操作,并且在写操作期间,所有的读操作都必须等待。
💧 读写锁的核心思想是:读操作之间是可以并发执行的,而写操作是独占的,即不能与其他读操作或者写操作同时执行
1.2 读写锁的工作原理及特点
- 即读锁允许多个线程同时获得,因为读操作本身是线程安全的;而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的
🐇 读写锁的特点:
- 读读不互斥:多个读线程可以同时访问共享资源
- 读写互斥:读操作和写操作互斥,即在写操作进行时,其他线程不能进行读或写操作
- 写写互斥:多个写线程不能同时进行写操作
🐇 具体来说,读写锁的行为如下:
-
读操作(共享锁):
- 如果没有线程正在持有写锁,那么多个线程可以同时获得读锁并执行读取操作。
- 读操作不会阻塞其他读操作,但会阻塞写操作。
-
写操作(独占锁):
- 写操作会阻塞所有其他的读操作和写操作。换句话说,在某个线程持有写锁期间,其他线程既无法获得读锁也无法获得写锁。
- 写操作是独占的,确保在操作过程中共享数据的一致性
1.3 读写锁的实现
🥝 读写锁相关函数
在书写具体代码之前,我们先来了解一下其相关函数
① 初始化锁
原型:
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);参数:rwlock: 指向 pthread_rwlock_t 类型的读写锁对象的指针。attr: 一个指向 pthread_rwlockattr_t 类型的指针,可以设置锁的属性。如果不需要特定的属性,通常可以将其设置为 NULL。返回值:
返回 0 表示成功,返回错误码(如 EINVAL)表示初始化失败。范例:
#include <pthread.h>
#include <stdio.h>pthread_rwlock_t rwlock;int main() {// 初始化读写锁if (pthread_rwlock_init(&rwlock, NULL) != 0) {perror("pthread_rwlock_init");return 1;}// 后续可以使用 rwlock 执行读写操作// 销毁读写锁pthread_rwlock_destroy(&rwlock);return 0;
}
🎈 pthread_rwlock_init 用于初始化一个读写锁。该函数会创建一个 pthread_rwlock_t 类型的读写锁变量,使其处于初始化状态,供后续的线程操作使用
② 获取锁
a. 读锁
原型:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);参数:
rwlock: 指向要获取读锁的 pthread_rwlock_t 类型的读写锁对象。
返回值:返回 0 表示成功,返回错误码表示失败(例如 EBUSY 表示写锁被持有,当前线程无法获得读锁)范例:
#include <pthread.h>
#include <stdio.h>pthread_rwlock_t rwlock;void* read_data(void* arg) {pthread_rwlock_rdlock(&rwlock); // 获取读锁printf("Reading data...\n");pthread_rwlock_unlock(&rwlock); // 释放读锁return NULL;
}int main() {// 初始化读写锁pthread_rwlock_init(&rwlock, NULL);pthread_t threads[3];for (int i = 0; i < 3; i++) {pthread_create(&threads[i], NULL, read_data, NULL);}for (int i = 0; i < 3; i++) {pthread_join(threads[i], NULL);}pthread_rwlock_destroy(&rwlock); // 销毁读写锁return 0;
}
// 在上述代码中,多个线程可以同时获得读锁并执行读取操作,而无需相互阻塞。
🎈 pthread_rwlock_rdlock 用于获取读锁,即共享锁。多个线程可以同时持有读锁进行读取操作,但在任何时刻,写锁无法被获得,直到所有的读锁都被释放。
b. 写锁
原型:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);参数:
rwlock: 指向要获取写锁的 pthread_rwlock_t 类型的读写锁对象。返回值:
返回 0 表示成功,返回错误码表示失败如: EBUSY 表示有其他线程持有读锁或写锁,当前线程无法获得写锁范例:
#include <pthread.h>
#include <stdio.h>pthread_rwlock_t rwlock;void* write_data(void* arg) {pthread_rwlock_wrlock(&rwlock); // 获取写锁printf("Writing data...\n");pthread_rwlock_unlock(&rwlock); // 释放写锁return NULL;
}int main() {// 初始化读写锁pthread_rwlock_init(&rwlock, NULL);pthread_t threads[2];for (int i = 0; i < 2; i++) {pthread_create(&threads[i], NULL, write_data, NULL);}for (int i = 0; i < 2; i++) {pthread_join(threads[i], NULL);}pthread_rwlock_destroy(&rwlock); // 销毁读写锁return 0;
}// 在上面的示例中,write_data 函数每次都获取写锁,并在完成写操作后释放写锁。
// 在写操作期间,不允许其他线程获取读锁或写锁
🎈 pthread_rwlock_wrlock 用于获取写锁。写锁是独占的,即任何一个线程持有写锁时,其他线程不能获得读锁或写锁。只有当所有线程都释放了读锁,写锁才能被获取。
③ 释放锁
原型:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);参数:
rwlock: 指向需要释放锁的 pthread_rwlock_t 类型的读写锁对象。返回值:
返回 0 表示成功,返回错误码表示失败(例如 EINVAL 表示锁没有被当前线程持有)
🎈 每当一个线程完成对共享资源的读或写操作时,它需要释放相应的锁。pthread_rwlock_unlock 用于释放由 pthread_rwlock_rdlock 或 pthread_rwlock_wrlock 获得的锁。
🥝 案例如下:
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstdlib>
#include <ctime>// 共享资源
int shared_data = 0;// 读者锁
pthread_rwlock_t rwlock;// 读者线程函数
void *Reader(void *arg)
{// slee(1); // 读者优先, 一旦读者进入 && 读者很多,写者就进不去了int number = *(int*)arg;while(true){pthread_rwlock_rdlock(&rwlock); // 读者加锁std::cout << "读者- " << number << " 正在读取数据, 数据是: " << shared_data << std::endl;sleep(1); // 模拟读取操作pthread_rwlock_unlock(&rwlock); // 解锁}delete (int*)arg;return NULL;
}// 写者线程函数
void *Writer(void *arg)
{int number = *(int*)arg;while(true){pthread_rwlock_wrlock(&rwlock); // 写者加锁shared_data = rand() % 100; // 修改共享数据std::cout << "写者- " << number << " 正在写入, 新的数据是: " << shared_data << std::endl;sleep(2); // 模拟写入操作pthread_rwlock_unlock(&rwlock); // 解锁}delete (int*)arg;return NULL;
}int main()
{srand(time(nullptr)^getpid());pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁// 可以提高读写数量配比,观察现象const int reader_num = 2;const int writer_num = 2;const int total = reader_num + writer_num;pthread_t threads[total]; // 假设读者和写者数量相等// 创建读者线程for(int i = 0; i < reader_num; ++i){int *id = new int(i);pthread_create(&threads[i], NULL, Reader, id);}// 创建写者线程for(int i = reader_num; i < total; ++i){int *id = new int(i - reader_num);pthread_create(&threads[i], NULL, Writer, id);}// 等待所有线程完成for(int i = 0; i < total; ++i){pthread_join(threads[i], NULL);}pthread_rwlock_destroy(&rwlock); // 销毁读写锁return 0;
}
输出结果如下:
分析:
- 上面我们创建了 两个读者、两个写者,读者线程每次获取读锁并且读取数据后休眠一秒,而写者线程每次获取写锁后休眠 2 s,这种不对称休眠可能会使写者长时间处于等待
- 写锁,它是独占的,意味着任何时候只能有 一个线程 获得写锁。即使你创建了两个写者线程,只有一个线程能在某一时刻获得写锁,另一个线程必须等待,直到当前持有写锁的线程释放它。
- 因此大部分情况,我们都只看到读锁读取数据,少部分情况出现写锁写入
那么我们做出一些修改,比如让读锁在加锁前就休眠 1 s
则出现大部分情况都是写者正在写入,读者很难读取到
同样地,我们也可以修改读者、写者数量来改变输出情况,大家也可以自己试试,我就不多尝试了
1.4 读写锁优缺点及应用场景
🍉 优点:
- 并发性:读操作的并发执行大大提高了系统的吞吐量,尤其是当读操作远多于写操作时,读写锁能够显著提高性能。
- 资源共享:多个线程可以在不干扰彼此的情况下同时读取共享资源,提高了资源利用率。
🍉 缺点:
虽然读写锁提高了并发性能,但它也有一些潜在的缺点:
-
写操作的饥饿问题:如果系统中有大量的读操作而写操作很少发生,可能会导致写线程长期得不到执行。这种现象被称为写操作的 饥饿(starvation)。一些实现会通过公平策略来解决这个问题。
-
实现复杂性:相比普通的互斥锁,读写锁的实现更复杂,需要管理多个线程的访问请求,可能会导致死锁或者性能下降,尤其是在高并发环境下。
-
性能瓶颈:在某些场景下,读写锁的性能提升可能并不显著,特别是在写操作占主导地位时,锁的争用可能导致性能瓶颈
🍉 读写锁应用场景:
- 读操作频繁,写操作较少:如果你的应用中读操作远多于写操作,使用读写锁可以显著提高性能,因为多个线程可以同时执行读操作,而写操作则需要独占锁,避免了不必要的阻塞。示例:数据库缓存、Web 服务器的请求处理等。
- 不需要严格顺序的读操作:读操作之间不需要顺序或依赖关系时,多个线程可以并发执行读操作,提高并发性。
1.5 性能开销:读写锁 VS 互斥锁
- 读写锁的性能开销与普通互斥锁相比,通常情况下读写锁的单次加锁开销大于互斥锁。
- 这是因为读写锁需要进行额外的引用计数和加锁读写性质判别,这些操作增加了其复杂性和性能开销。
- 在设计上,读写锁比互斥锁更复杂,其内部加锁和解锁的逻辑也更为复杂,需要更新读者和写者的数量,而互斥锁则无需这样的操作。
- 然而,读写锁的性能优势在于其在读多写少的场景下表现更佳。
- 当读操作远多于写操作时,读写锁可以允许多个读线程同时访问共享资源,从而提高并发性能。
- 相反,在写操作频繁的情况下,读写锁的性能会比互斥锁差,因为写操作必须互斥进行,读写锁需要处理额外的逻辑来避免写锁“饥饿”
1.6 优先策略
🦋 读者优先(Reader-Preference)
- 在这种策略中, 系统会尽可能多地允许多个读者同时访问资源(比如共享文件或数据) , 而不会优先考虑写者。 这意味着当有读者正在读取时, 新到达的读者会立即被允许进入读取区, 而写者则会被阻塞, 直到所有读者都离开读取区。 读者优先策略可能会导致写者饥饿(即写者长时间无法获得写入权限) , 特别是当读者频繁到达时。
🦋 写者优先(Writer-Preference)
- 在这种策略中, 系统会优先考虑写者。 当写者请求写入权限时, 系统会尽快地让写者进入写入区, 即使此时有读者正在读取。 这通常意味着一旦有写者到达, 所有后续的读者都会被阻塞, 直到写者完成写入并离开写入区。 写者优先策略可以减少写者等待的时间, 但可能会导致读者饥饿(即读者长时间无法获得读取权限) , 特别是当写者频繁到达时。
2. 自旋锁 🔐
2.1 基本概念
🔥 自旋锁(Spinlock)是一种简单的同步机制,用于在多线程或多核系统中防止并发访问共享资源。在获取锁时,如果锁被其他线程占用,线程并不会进入休眠状态,而是不断地重复检查锁是否可用,这个过程就被称为“自旋”
2.2 自旋锁的原理
🐳 自旋锁通常使用一个共享的标志位(如一个布尔值)来表示锁的状态。当标志位为true 时,表示锁已被某个线程占用;当标志位为false 时,表示锁可用。当一个线程尝试获取自旋锁时,它会不断检查标志位:
如果标志位为 false,表示锁可用,线程将设置标志位为true,表示自己占用了锁,并进入临界区。
如果标志位为 true(即锁已被其他线程占用),线程会在一个循环中不断自旋等待,直到锁被释放。
2.3 自旋锁实现
自旋锁的实现通常使用原子操作来保证操作的原子性,常用的软件实现方式是通过 CAS(Compare-And-Swap)指令实现。以下是一个简单的自旋锁实现示例(伪代码):
#include <stdio.h>
#include <stdatomic.h>
#include <pthread.h>
#include <unistd.h>
// 使用原子标志来模拟自旋锁
atomic_flag spinlock = ATOMIC_FLAG_INIT; // ATOMIC_FLAG_INIT 是 0
// 尝试获取锁
void spinlock_lock() {while (atomic_flag_test_and_set(&spinlock)) {// 如果锁被占用,则忙等待}
}
// 释放锁
void spinlock_unlock() {atomic_flag_clear(&spinlock);
}typedef _Atomic struct
{
#if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1_Bool __val;
#elseunsigned char __val;
#endif
}atomic_flag;
功能描述
- atomic_flag_test_and_set 函数检查 atomic_flag 的当前状态。
- 如果atomic_flag 之前没有被设置过(即其值为 false 或“未设置”状态),则函数会将其设置为 true(或“设置”状态),并返回先前的值(在这种情况下为 false)。
- 如果atomic_flag 之前已经被设置过(即其值为 true),则函数不会改变其状态,但会返回 true。
原子性
- 这个操作是原子的,意味着在多线程环境中,它保证了对 atomic_flag 的读取和修改是不可分割的。当一个线程调用此函数时,其他线程无法看到这个操作的任何中间状态,这确保了操作的线程安全性。
Linux 提供的自旋锁系统调用
#include <pthread.h>int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);
注意事项
- 使用自旋锁时,需要确保锁被释放的时间尽可能短,以避免 CPU 资源的浪费。
- 在多 CPU 环境下,自旋锁可能不如其他锁机制高效,因为它可能导致线程在不同的 CPU 上自旋等待。
结论
- 自旋锁是一种适用于短时间内锁竞争情况的同步机制,它通过减少线程切换的开销来提高锁操作的效率。然而,它也存在 CPU 资源浪费和可能引起活锁等缺点。
- 在使用自旋锁时,需要根据具体的应用场景进行选择,并确保锁被释放的时间尽可能短。
案例: 抢票卖票
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>int ticket = 1000;
pthread_spinlock_t lock;void *routine(void *arg)
{char *id = (char*)arg;while(1){if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}int main(){pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, routine, (void *) "thread-1");pthread_create(&t2, NULL, routine, (void *) "thread-2");pthread_create(&t3, NULL, routine, (void *) "thread-3");pthread_create(&t4, NULL, routine, (void *) "thread-4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
还记得我们之前写的买票问题嘛,这个最后由于竞争问题,导致最后会出现负值,如下:
加上自旋锁,修改如下:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>int ticket = 1000;
pthread_spinlock_t lock;void *routine(void *arg)
{char *id = (char*)arg;while(1){pthread_spin_lock(&lock);if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_spin_unlock(&lock);}else{pthread_spin_unlock(&lock);break;}}return nullptr;
}int main()
{pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, routine, (void *) "thread-1");pthread_create(&t2, NULL, routine, (void *) "thread-2");pthread_create(&t3, NULL, routine, (void *) "thread-3");pthread_create(&t4, NULL, routine, (void *) "thread-4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);return 0;
}
相比于之前的互斥锁,这里我们把 加锁放到了 while 循环内部,因为需要一直自检锁,此时结果如下:
2.4 自旋锁优缺点及应用场景
🥑 优点:
- 高效的短期锁竞争:如果锁持有时间很短,自旋锁可以非常高效,因为它避免了线程上下文切换的开销。
- 实现简单:自旋锁的实现通常非常简单,基本上只需要一个标志位(flag)和原子操作
- 低延迟:自旋锁适用于短时间内的锁竞争情况,因为它不会让线程进入休眠状
态,从而避免了线程切换的开销,提高了锁操作的效率。 - 减少系统调度开销:等待锁的线程不会被阻塞,不需要上下文切换,从而减少了
系统调度的开销
🥑 缺点:
- 高 CPU 使用率:如果线程需要等待很长时间才能获取锁,自旋锁会造成 CPU 时间片的浪费,因为线程会在忙等期间占用 CPU 资源。
- 可能导致优先级反转:如果一个低优先级的线程持有锁,而高优先级线程自旋等待,会造成优先级反转的问题(此问题可以通过其他技术如优先级继承来解决)。
- 不适用于长时间锁持有:如果锁的持有时间较长,自旋锁并不适合,因为自旋等待会导致极大的性能问题
- 可能引起活锁:当多个线程同时自旋等待同一个锁时,如果没有适当的退避策略,可能会导致所有线程都在不断检查锁状态而无法进入临界区,形成活锁
🥑使用场景:
- 短暂等待的情况:适用于锁被占用时间很短的场景,如多线程对共享数据进行简单的读写操作。
- 多线程锁使用:通常用于系统底层,同步多个 CPU 对共享资源的访问
- 锁的持有时间非常短:例如,对于某些非常简单的操作(如计数器的增减),自旋锁可以有效减少线程上下文切换的开销。
- 锁争用较少:如果多个线程竞争同一个锁的概率较小,则使用自旋锁可以减少等待时间,避免上下文切换的开销。
2.5 自旋锁 VS 互斥锁
🔥 与传统的互斥锁(Mutex)不同,互斥锁通常会让线程在无法获得锁时进入休眠状态,减少 CPU 的浪费,而自旋锁则在锁被占用时不断轮询,直到获取到锁。这种方式通常用于锁持有时间较短的场景,因为自旋锁避免了线程切换的开销,但是不合理的使用,可能也会浪费 CPU 资源
3. 共勉 🔥
【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【Linux】的内容,请持续关注我 !!
相关文章:

【Linux】:多线程(读写锁 自旋锁)
✨ 倘若南方知我意,莫将晚霞落黄昏 🌏 📃个人主页:island1314 🔥个人专栏:Linux—登神长阶 ⛺️ 欢迎关注:👍点赞 &#…...

Java开发 PDF文件生成方案
业务需求背景 业务端需要能够将考试答卷内容按指定格式呈现并导出为pdf格式进行存档,作为紧急需求插入。导出内容存在样式复杂性,包括特定的字体(中文)、字号、颜色,页面得有页眉、页码,数据需要进行表格聚…...

数学期望和方差
数学期望(Mathematical Expectation)和方差(Variance)是概率论和统计学中两个非常重要的概念。下面将分别对这两个概念进行解释。 数学期望 数学期望是随机变量的平均值,它描述了随机变量的中心位置。对于离散随机变…...

【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍Tensor RT 的优化流程。
【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍Tensor RT 的优化流程。 【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍Tensor RT 的优化流程。 文章目录 【面试AI算法题中的知识点】方向涉及:ML/D…...

BLDC无感控制的驱动逻辑
如何知道转子已经到达预定位置,因为我们只有知道了转子到达了预定位置之后才能进行换相,这样电机才能顺滑的运转。转子位置检测常用的有三种方式。 方式一:通过过零检测,三相相电压与电机中性点电压进行比较。过零检测的优点在于…...
BP神经网络的反向传播算法
BP神经网络(Backpropagation Neural Network)是一种常用的多层前馈神经网络,通过反向传播算法进行训练。反向传播算法的核心思想是通过计算损失函数对每个权重的偏导数,从而调整权重,使得网络的预测输出与真实输出之间…...

[实用指南]如何将视频从iPhone传输到iPad
概括 将视频从 iPhone 传输到 iPad 时遇到问题?您可能知道一种方法,但不知道如何操作。此外,您要传输的视频越大,完成任务就越困难。那么如何将视频从 iPhone 传输到 iPad,特别是当您需要发送大视频文件时?…...

Linux Snipaste 截图闪屏/闪烁
防 csdn 不能看,Go to juejin Linux Snipaste 截图时窗口元素一闪一闪的无法正常使用。 解决此问题时系统环境为 Manjaro KDE6,不过我在其他发行版与 gnome 上也碰到了。 先放解决办法: # 启动 Snipaste 时去掉缩放参数 env -u QT_SCREEN_…...

【YOLOv5】源码(common.py)
该文件位于/models/common.py,提供了构建YOLOv5模型的各种基础模块,其中包含了常用的功能模块,如自动填充autopad函数、标准卷积层Conv、瓶颈层Bottleneck、C3、SPPF、Concat层等 参考笔记:【YOLOv3】 源码(common.py…...

Node 如何生成 RSA 公钥私钥对
一、引入crypto模块 crypto 为node 自带模块,无需安装 const crypto require(crypto);二、封装生成方法 async function generateRSAKeyPair() {return new Promise((resolve, reject) > {crypto.generateKeyPair(rsa, {modulusLength: 2048, // 密钥长度为 …...

瑞_Linux中部署配置Java服务并设置开机自启动
文章目录 背景Linux服务配置步骤并设置开机自启动附-Linux服务常用指令 🙊 前言:由于博主在工作时,需要将服务部署到 Linux 服务器上运行,每次通过指令启动服务非常麻烦,所以将 jar 包部署的服务设置开机自启动&#x…...

javaEE-多线程进阶-JUC的常见类
juc:指的是java.util.concurrent包,该包中加载了一些有关的多线程有关的类。 目录 一、Callable接口 FutureTask类 参考代码: 二、ReentrantLock 可重入锁 ReentrantLock和synchronized的区别: 1.ReentantLock还有一个方法:…...

Flume拦截器的实现
Flume conf文件编写 vim file_to_kafka.conf#定义组件 a1.sources r1 a1.channels c1#配置source a1.sources.r1.type TAILDIR a1.sources.r1.filegroups f1 a1.sources.r1.filegroups.f1 /Users/zhangjin/model/project/realtime-flink/applog/log/app.* # 设置断点续传…...

Swift Combine 学习(四):操作符 Operator
Swift Combine 学习(一):Combine 初印象Swift Combine 学习(二):发布者 PublisherSwift Combine 学习(三):Subscription和 SubscriberSwift Combine 学习(四&…...

leetcode 173.二叉搜索树迭代器栈绝妙思路
以上算法题中一个比较好的实现思路就是利用栈来进行实现,以下方法三就是利用栈来进行实现的,思路很好,很简练。进行next的时候,先是一直拿到左边的子树,直到null为止,这一步比较好思考一点,下一…...

df.groupby([pd.Grouper(freq=‘1M‘, key=‘Date‘), ‘Buyer‘]).sum()
df.groupby([pd.Grouper(freq1M, keyDate), Buyer]).sum() 用于根据特定的时间频率和买家(Buyer)对 DataFrame 进行分组,然后计算每个分组的总和。下面是对这行代码的逐步解释: df.groupby([...]):这个操作会根据传入的…...

LLM - 使用 LLaMA-Factory 部署大模型 HTTP 多模态服务 (4)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144881432 大模型的 HTTP 服务,通过网络接口,提供 AI 模型功能的服务,允许通过发送 HTTP 请求,交互…...

icp备案网站个人备案与企业备案的区别
个人备案和企业备案是在进行ICP备案时需要考虑的两种不同情况。个人备案是指个人拥有的网站进行备案,而企业备案则是指企业或组织名下的网站进行备案。这两者在备案过程中有一些明显的区别。 首先,个人备案相对来说流程较为简单。个人备案只需要提供个人…...

如何不修改模型参数来强化大语言模型 (LLM) 能力?
前言 如果你对这篇文章感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 大语言模型 (Large Language Model, LLM, e.g. ChatGPT) 的参数量少则几十亿,多则上千亿,对其的训…...

AF3 AtomAttentionEncoder类的init_pair_repr方法解读
AlphaFold3 的 AtomAttentionEncoder 类中,init_pair_repr 方法方法负责为原子之间的关系计算成对表示(pair representation),这是原子转变器(atom transformer)模型的关键组成部分,直接影响对蛋白质/分子相互作用的建模。 init_pair_repr源代码: def init_pair_repr(…...

DDoS攻击防御方案大全
1. 引言 随着互联网的迅猛发展,DDoS(分布式拒绝服务)攻击成为了网络安全领域中最常见且危害严重的攻击方式之一。DDoS攻击通过向目标网络或服务发送大量流量,导致服务器过载,最终使其无法响应合法用户的请求。本文将深…...

Vue中常用指令
一、内容渲染指令 1.v-text:操作纯文本,用于更新标签包含的文本,但是使用不灵活,无法拼接字符串,会覆盖文本,可以简写为{{}},{{}}支持逻辑运算。 用法示例: //把name对应的值渲染到…...

Servlet解析
概念 Servlet是运行在服务端的小程序(Server Applet),可以处理客户端的请求并返回响应,主要用于构建动态的Web应用,是SpringMVC的基础。 生命周期 加载和初始化 默认在客户端第一次请求加载到容器中,通过反射实例化…...

带虚继承的类对象模型
文章目录 1、代码2、 单个虚继承3、vbptr是什么4、虚继承的多继承 1、代码 #include<iostream> using namespace std;class Base { public:int ma; };class Derive1 :virtual public Base { public:int mb; };class Derive2 :public Base { public:int mc; };class Deri…...

深度学习中的离群值
文章目录 深度学习中有离群值吗?深度学习中的离群值来源:处理离群值的策略:1. 数据预处理阶段:2. 数据增强和鲁棒模型:3. 模型训练阶段:4. 异常检测集成模型: 如何处理对抗样本?总结…...

如何利用Logo设计免费生成器创建专业级Logo
在当今的商业世界中,一个好的Logo是品牌身份的象征,它承载着公司的形象与理念。设计一个专业级的Logo不再需要花费大量的金钱和时间,尤其是当我们拥有Logo设计免费生成器这样的工具时。接下来,让我们深入探讨如何利用这些工具来创…...

Mysql SQL 超实用的7个日期算术运算实例(10k)
文章目录 前言1. 加上或减去若干天、若干月或若干年基本语法使用场景注意事项运用实例分析说明2. 确定两个日期相差多少天基本语法使用场景注意事项运用实例分析说明3. 确定两个日期之间有多少个工作日基本语法使用场景注意事项运用实例分析说明4. 确定两个日期相隔多少个月或多…...

运算指令(PLC)
加 ADD 减 SUB 乘 MUL 除 DIV 浮点运算 整数运算...

「Mac畅玩鸿蒙与硬件49」UI互动应用篇26 - 数字填色游戏
本篇教程将带你实现一个数字填色小游戏,通过简单的交互逻辑,学习如何使用鸿蒙开发组件创建趣味性强的应用。 关键词 UI互动应用数字填色动态交互逻辑判断游戏开发 一、功能说明 数字填色小游戏包含以下功能: 数字选择:用户点击…...

机器学习经典算法——逻辑回归
目录 算法介绍 算法概念 算法的优缺点 LogisticRegression()函数理解 环境准备 算法练习 算法介绍 算法概念 逻辑回归(Logistic Regression)是一种广泛应用于分类问题的机器学习算法。 它基于线性回归的思想,但通过引入一个逻辑函数&…...