Linux系统编程-线程同步详解

线程同步是指多个线程协调工作,以便在共享资源的访问和操作过程中保持数据一致性和正确性。在多线程环境中,线程是并发执行的,因此如果多个线程同时访问和修改共享资源,可能会导致数据不一致、竞态条件(race condition)等问题。线程同步通过协调线程的执行顺序和共享资源的访问来避免这些问题。

在多线程编程中,需要线程同步的主要原因包括:

  • 共享资源的安全访问:多个线程可能同时访问和修改共享的数据或资源,如果没有同步机制,可能会导致数据不一致或损坏。

  • 避免竞态条件:竞态条件是指多个线程以不受控制的顺序访问共享资源,从而导致程序执行结果不确定的情况。通过同步机制可以避免竞态条件的发生。

  • 保证程序正确性:线程同步确保多线程程序的行为是可预测和正确的,避免因并发访问而引入的错误。

1.互斥量(Mutex)

互斥量(互斥锁)是一种最基本的同步机制,用于保护临界区(Critical Section),确保在同一时间只有一个线程可以访问共享资源。

它提供了两个主要操作:

  • 加锁(Locking):线程通过加锁操作获取互斥量,如果互斥量已经被其他线程锁定,则当前线程会阻塞,直到获取到锁。
  • 解锁(Unlocking):线程使用解锁操作释放互斥量,允许其他线程获取锁。

通过这种机制,互斥量确保了临界区中的代码在同一时间只能被一个线程执行,从而避免了数据竞争和不一致的问题。

1.1初始化和销毁互斥量

1.1.1pthread_mutex_init()

初始化互斥量,并可选地设置其属性。

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数:
pthread_mutex_t *mutex:指向互斥量变量的指针,用于初始化该互斥量。
const pthread_mutexattr_t *attr:可选参数,指向 pthread_mutexattr_t 类型的指针,用于设置互斥量的属性。如果为 NULL,使用默认属性。
返回值:
成功:返回 0。
失败:返回错误号(例如 EINVAL 表示参数无效)。
  • 初始化互斥量后需要调用 pthread_mutex_destroy 函数来释放其占用的资源。
  • 通常在使用互斥量前调用,确保互斥量的准备和设置。

1.1.2pthread_mutex_destroy()

销毁互斥量,释放其占用的资源。

int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:指向要销毁的互斥量变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 在互斥量不再需要时调用,以释放其占用的资源。
  • 仅当确保没有线程在使用该互斥量时才能安全地调用该函数。

1.2加锁和解锁操作

1.2.1pthread_mutex_lock()

加锁操作,阻塞当前线程直到获取锁。

int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:指向要加锁的互斥量变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 当互斥量已经被其他线程锁定时,当前线程会阻塞直到获取锁。
  • 必须成对使用 pthread_mutex_lockpthread_mutex_unlock 来保护临界区。

1.2.2pthread_mutex_trylock()

尝试加锁操作,非阻塞式,立即返回结果。

int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:指向要加锁的互斥量变量的指针。
返回值:
成功:返回 0。
失败:返回 EBUSY(互斥量已被锁定)或其他错误号。
  • 如果互斥量已被锁定,则立即返回错误。
  • 适用于需要非阻塞尝试加锁的情况,可以用于避免线程阻塞。

1.2.3pthread_mutex_unlock()

解锁操作,释放互斥量。

int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex:指向要解锁的互斥量变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 解锁后允许其他线程获取互斥量。
  • 必须在每次成功调用 pthread_mutex_lock 后调用 pthread_mutex_unlock 来释放锁。

示例代码:

创建两个线程来共享一个全局变量 int number,然后每个线程分别对其进行5000次递增操作,同时使用互斥量来保证线程同步。(如果没有锁大家可以看看会发生什么情况)

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

#define NUM_THREADS 2
#define NUM_INCREMENTS 5000

int number = 0;
pthread_mutex_t mutex;  // 互斥量变量

void* thread_function(void* arg) {
    int thread_id = *(int*)arg;

    for (int i = 0; i < NUM_INCREMENTS; ++i) {
        // 加锁
        pthread_mutex_lock(&mutex);

        // 临界区:对共享变量 number 执行操作
        number++;

        // 解锁
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];

    // 初始化互斥量
    if (pthread_mutex_init(&mutex, NULL) != 0) {
        fprintf(stderr, "Mutex initialization failed\n");
        return 1;
    }

    // 创建两个线程
    for (int i = 0; i < NUM_THREADS; ++i) {
        thread_ids[i] = i;
        if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
            fprintf(stderr, "Thread creation failed\n");
            return 1;
        }
    }

    // 等待所有线程结束
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(threads[i], NULL);
    }

    // 销毁互斥量
    pthread_mutex_destroy(&mutex);

    // 输出最终的 number 值
    printf("Final value of number: %d\n", number);

    return 0;
}

1.3死锁(Deadlock)

死锁并不是linux提供给用户的一种使用方法,而是由于用户使用互斥锁不当引起的一种现象。死锁发生在多个并发执行的线程(或进程)之间,主要由于彼此竞争资源而造成。典型的死锁情况涉及两个或多个线程或进程,每个都在等待对方释放其持有的资源,导致所有线程都被阻塞,无法继续执行下去。

常见的死锁有两种:

第一种:自己锁自己,如下图代码片段

第二种 线程A拥有A锁,请求获得B锁;线程B拥有B锁,请求获得A锁,这样造成线程A和线程B都不释放自己的锁,而且还想得到对方的锁,从而产生死锁,如下图所示:

  • 如何解决死锁:
  • 让线程按照一定的顺序去访问共享资源
  • 在访问其他锁的时候,需要先将自己的锁解开
  • 调用pthread_mutex_trylock,如果加锁不成功会立刻返回

2. 条件变量(Condition Variable)

条件变量(Condition Variable) 是一种线程间同步的机制,通常与互斥锁结合使用,用于在某个条件满足时通知其他线程。条件变量允许线程在等待某个特定条件的同时阻塞,直到另一个线程显式地通知条件已经满足或者超时。条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

  • 使用互斥量保护共享数据。
  • 使用条件变量可以使线程阻塞, 等待某个条件的发生, 当条件满足的时候解除阻塞。

主要作用包括:

  • 等待条件:使线程能够在满足特定条件之前进入休眠状态,节省系统资源。
  • 条件满足时通知:一旦其他线程改变了条件,可以通过条件变量通知正在等待的线程继续执行。

2.1相关操作函数

2.1.1pthread_cond_init()

初始化条件变量 cond

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数:
pthread_cond_t *restrict cond:指向要初始化的条件变量的指针。
const pthread_condattr_t *restrict attr:可选参数,指向 pthread_condattr_t 类型的指针,用于设置条件变量的属性。通常可以设置为 NULL,表示使用默认属性。
返回值:
成功:返回 0。
失败:返回错误号(例如 EINVAL 表示参数无效)。
  • 初始化条件变量后,应当使用 pthread_cond_destroy 函数来释放其占用的资源。
  • 通常在创建条件变量后立即调用该函数进行初始化。

2.1.2pthread_cond_destroy()

销毁条件变量 cond,释放其占用的资源。

int pthread_cond_destroy(pthread_cond_t *cond);
参数:
pthread_cond_t *cond:指向要销毁的条件变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 在条件变量不再需要时调用,以释放其占用的资源。
  • 确保没有线程在使用该条件变量时才能安全地调用该函数。

2.1.3pthread_cond_wait()

阻塞当前线程,等待条件变量 cond 被其他线程信号唤醒。

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数:
pthread_cond_t *restrict cond:指向要等待的条件变量的指针。
pthread_mutex_t *restrict mutex:与条件变量相关联的互斥锁,用于避免竞态条件。
返回值:
成功:返回 0。
失败:返回错误号。
  • 调用该函数前必须先获取 mutex 锁,函数内部会自动释放 mutex 锁,并在等待期间阻塞当前线程。
  • 当被唤醒时,函数内部会再次获取 mutex 锁,并从函数返回。

2.1.4pthread_cond_timedwait()

在指定的超时时间内阻塞当前线程,等待条件变量 cond 被其他线程信号唤醒。

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参数:
pthread_cond_t *restrict cond:指向要等待的条件变量的指针。
pthread_mutex_t *restrict mutex:与条件变量相关联的互斥锁。
const struct timespec *restrict abstime:指定的超时时间,为绝对时间。
返回值:
成功:返回 0。
失败:返回错误号。
  • pthread_cond_wait 类似,但可以设置超时时间,在超时之后会自动返回并解除阻塞。
  • 如果超时时间设置为 NULL,则函数将无限期地等待,直到条件变量被信号唤醒。

2.1.5pthread_cond_signal()

唤醒等待在条件变量 cond 上的一个线程。

int pthread_cond_signal(pthread_cond_t *cond);
参数:
pthread_cond_t *cond:指向要唤醒的条件变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 唤醒等待在条件变量上的一个线程,如果有多个线程等待,则可能唤醒任意一个。
  • 唤醒后,被唤醒的线程将尝试重新获取与条件变量关联的互斥锁。

示例代码:

使用条件变量实现生产者与消费者模型

3. 读写锁(Read-Write Lock)

读写锁允许多个线程同时对共享资源进行读取操作,但是写操作时需要排他性,即同一时刻只能有一个线程进行写操作。这种区分读和写的方式能够有效地提高系统的并发性能,特别适用于数据结构中读操作远远多于写操作的情况。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。

读写锁特性:

  • 读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞。
  • 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
  • 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

3.1初始化和销毁读写锁

3.1.1pthread_rwlock_init()

初始化读写锁 rwlock,可以选择性地设置其属性。

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
参数:
pthread_rwlock_t *restrict rwlock:指向要初始化的读写锁变量的指针。
const pthread_rwlockattr_t *restrict attr:可选参数,指向 pthread_rwlockattr_t 类型的指针,用于设置读写锁的属性。通常可以设置为 NULL,表示使用默认属性。
返回值:
成功:返回 0。
失败:返回错误号(例如 EINVAL 表示参数无效)。
  • 初始化读写锁后,应当使用 pthread_rwlock_destroy 函数来释放其占用的资源。
  • 通常在创建读写锁后立即调用该函数进行初始化。

3.1.2pthread_rwlock_destroy()

销毁读写锁 rwlock,释放其占用的资源。

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:
pthread_rwlock_t *rwlock:指向要销毁的读写锁变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 在读写锁不再需要时调用,以释放其占用的资源。
  • 确保没有线程在使用该读写锁时才能安全地调用该函数。

3.2读写锁加锁和解锁操作:

3.2.1pthread_rwlock_rdlock()

加读锁,允许多个线程同时对共享资源进行读取操作。

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
参数:
pthread_rwlock_t *rwlock:指向要加读锁的读写锁变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 如果有其他线程持有写锁或者有线程在请求写锁,则当前线程将被阻塞,直到获取读锁为止。
  • 多个线程可以同时获取读锁,不会相互阻塞。

3.2.2pthread_rwlock_wrlock()

加写锁,确保只有一个线程可以对共享资源进行写操作,期间禁止其他线程的读或写操作。

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
参数:
pthread_rwlock_t *rwlock:指向要加写锁的读写锁变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 如果有其他线程持有读锁或写锁,则当前线程将被阻塞,直到获取写锁为止。
  • 只能有一个线程可以同时持有写锁,其他线程无法同时获取读锁或写锁。

3.2.3pthread_rwlock_tryrdlock()

尝试加读锁,非阻塞方式。如果不能立即获得锁,则立即返回。

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
参数:
pthread_rwlock_t *rwlock:指向要加读锁的读写锁变量的指针。
返回值:
成功:返回 0。
失败:返回 EBUSY(读写锁已被写锁占用)或其他错误号。
  • 如果不能立即获取读锁,则该函数立即返回失败。
  • 适用于需要检测是否可以立即读取共享资源的场景。

3.2.4pthread_rwlock_trywrlock()

尝试加写锁,非阻塞方式。如果不能立即获得锁,则立即返回。

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
参数:
pthread_rwlock_t *rwlock:指向要加写锁的读写锁变量的指针。
返回值:
成功:返回 0。
失败:返回 EBUSY(读写锁已被其他线程占用)或其他错误号。
  • 如果不能立即获取写锁,则该函数立即返回失败。
  • 适用于需要检测是否可以立即写入共享资源的场景。

3.2.5pthread_rwlock_unlock()

解锁操作,释放之前加的读锁或写锁。

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数:
pthread_rwlock_t *rwlock:指向要解锁的读写锁变量的指针。
返回值:
成功:返回 0。
失败:返回错误号。
  • 解锁后允许其他线程获取读锁或写锁。
  • 必须在每次成功调用 pthread_rwlock_rdlockpthread_rwlock_wrlock 后调用该函数来释放锁。

示例代码:

其中包括3个写线程和5个读线程对同一个全局资源进行操作。每个线程都会不定时地访问和修改这个全局资源,并通过读写锁确保线程之间的同步。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h> // 用于随机睡眠时间

#define NUM_READERS 5
#define NUM_WRITERS 3

pthread_rwlock_t rwlock;
int global_resource = 0;

void* writer(void* arg) {
    int thread_id = *(int*)arg;
    while (1) {
        // 模拟写操作
        sleep(rand() % 3); // 随机睡眠时间

        // 加写锁
        pthread_rwlock_wrlock(&rwlock);

        // 写操作
        global_resource++;
        printf("Writer %d writes: global_resource = %d\n", thread_id, global_resource);

        // 解锁
        pthread_rwlock_unlock(&rwlock);
    }
    return NULL;
}

void* reader(void* arg) {
    int thread_id = *(int*)arg;
    while (1) {
        // 模拟读操作
        sleep(rand() % 2); // 随机睡眠时间

        // 加读锁
        pthread_rwlock_rdlock(&rwlock);

        // 读操作
        printf("Reader %d reads: global_resource = %d\n", thread_id, global_resource);

        // 解锁
        pthread_rwlock_unlock(&rwlock);
    }
    return NULL;
}

int main() {
    pthread_t writers[NUM_WRITERS], readers[NUM_READERS];
    int writer_ids[NUM_WRITERS], reader_ids[NUM_READERS];
    int i;

    // 初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);

    // 创建写线程
    for (i = 0; i < NUM_WRITERS; ++i) {
        writer_ids[i] = i + 1;
        pthread_create(&writers[i], NULL, writer, &writer_ids[i]);
    }

    // 创建读线程
    for (i = 0; i < NUM_READERS; ++i) {
        reader_ids[i] = i + 1;
        pthread_create(&readers[i], NULL, reader, &reader_ids[i]);
    }

    // 等待所有写线程结束
    for (i = 0; i < NUM_WRITERS; ++i) {
        pthread_join(writers[i], NULL);
    }

    // 等待所有读线程结束
    for (i = 0; i < NUM_READERS; ++i) {
        pthread_join(readers[i], NULL);
    }

    // 销毁读写锁
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

4. 信号量(Semaphore)

信号量是由一个整型变量和相关的操作集合组成,用于控制对共享资源的访问。信号量可以看作是一个计数器,用于表示可用的资源数量,线程或进程在访问资源前必须首先获取信号量,访问结束后释放信号量。

主要作用包括:

  • 同步:控制多个线程或进程的执行顺序,保证在某些条件下的有序执行。
  • 互斥:保证对共享资源的访问是排他的,避免多个线程或进程同时修改资源造成的数据不一致性问题。

4.2相关操作函数

4.2.1sem_init()

初始化一个信号量 sem

int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem_t *sem:指向要初始化的信号量的指针。
int pshared:指定信号量是进程共享(非零)还是线程共享(零)。
unsigned int value:信号量的初始值,表示可用资源的数量。
返回值:
成功:返回 0。
失败:返回 -1,并设置 errno 来指示错误原因。
  • 信号量初始化后需要通过 sem_destroy 函数进行清理。
  • 如果 pshared 是非零,则信号量可以在多个进程间共享,通常使用在进程间通信(IPC)的场景中。

4.2.2sem_destroy()

销毁一个已经初始化的信号量 sem。释放由信号量占用的资源,确保在信号量不再需要时调用。

int sem_destroy(sem_t *sem);
参数:
sem_t *sem:指向要销毁的信号量的指针。
返回值:
成功:返回 0。
失败:返回 -1,并设置 errno 来指示错误原因。

4.2.3.sem_wait()

对信号量 sem 进行 P 操作(等待操作)。

int sem_wait(sem_t *sem);
参数:
sem_t *sem:指向要操作的信号量的指针。
返回值:
成功:返回 0。
失败:返回 -1,并设置 errno 来指示错误原因。
  • 如果信号量的值大于 0,将其递减;否则阻塞当前线程,直到信号量的值大于 0
  • 该函数执行时需要保证线程安全,通常与互斥锁结合使用以避免竞态条件。

4.2.4sem_trywait()

尝试对信号量 sem 进行 P 操作的非阻塞版本。与 sem_wait 不同的是,如果信号量的值为 0,立即返回而不阻塞线程。

int sem_trywait(sem_t *sem);
参数:
sem_t *sem:指向要操作的信号量的指针。
返回值:
成功:返回 0。
如果信号量的值为 0,表示资源不可用,立即返回 -1(不阻塞),并设置 errno 为 EAGAIN。
其他失败情况返回 -1,并设置 errno 来指示错误原因。

4.2.5sem_post()

对信号量 sem 进行 V 操作(释放操作)。

int sem_post(sem_t *sem);
参数:
sem_t *sem:指向要操作的信号量的指针。
返回值:
成功:返回 0。
失败:返回 -1,并设置 errno 来指示错误原因。
  • 将信号量的值递增,如果有线程因等待该信号量而被阻塞,将会唤醒其中一个线程。
  • 释放操作通常在资源使用完毕后调用,通知其他线程可以继续访问该资源。

4.2.6sem_getvalue()

获取信号量 sem 的当前值。sem_getvalue 函数可以获取信号量的当前值,而无需对其进行修改。

int sem_getvalue(sem_t *restrict sem, int *restrict sval);
参数:
sem_t *restrict sem:指向要获取值的信号量的指针。
int *restrict sval:用于存储信号量当前值的整型指针。
返回值:
成功:返回 0,并将当前信号量的值存储在 sval 中。
失败:返回 -1,并设置 errno 来指示错误原因。

示例代码:

使用信号量实现哲学家就餐问题

信号量演示代码:

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

#define NUM_THREADS 5

int shared_variable = 0;  // 共享的变量
sem_t mutex;  // 互斥信号量

void *thread_function(void *arg) {
    int thread_id = *((int *)arg);
    int local_variable = 0;
    
    while (1) {
        // 临界区开始
        sem_wait(&mutex);  // 等待互斥信号量,保证互斥访问共享资源
        local_variable = shared_variable;  // 读取共享变量到本地变量
        local_variable++;  // 修改本地变量
        sleep(rand() % 2);  // 模拟其他操作
        shared_variable = local_variable;  // 写回共享变量
        printf("线程 %d 执行后,共享变量的值为: %d\n", thread_id, shared_variable);
        sem_post(&mutex);  // 释放互斥信号量
        // 临界区结束
        
        sleep(rand() % 2 + 1);  // 模拟一段时间后再次操作
    }
    
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];
    
    // 初始化互斥信号量
    sem_init(&mutex, 0, 1);  // 初始值为1,表示资源未被占用
    
    // 创建线程
    for (int i = 0; i < NUM_THREADS; ++i) {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, thread_function, (void *)&thread_ids[i]);
    }
    
    // 等待线程结束
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(threads[i], NULL);
    }
    
    // 销毁信号量
    sem_destroy(&mutex);
    
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

15 - matlab m_map地学绘图工具基础函数 - 一些数据转换函数(二)

15 - matlab m_map地学绘图工具基础函数 - 一些数据转换函数&#xff08;二&#xff09; 0. 引言1. 关于m_geodesic2. 关于mygrid_sand23. 结语 0. 引言 通过前面篇节已经将m_map绘图工具中大多绘图有关的函数进行过介绍&#xff0c;已经能够满足基本的绘图需求&#xff0c;本节…

我想做信号通路分析,但我就是不想学编程

“我想做信号通路分析&#xff0c;但我就是不想学编程。” “我又不是生信狗&#xff0c;学代码会死。” “你们这些做生信的&#xff0c;整天把数据分析搞得神神秘秘&#xff0c;不就是怕被人抢饭碗而已嘛。” “这都没分析出我想要的结果&#xff0c;不靠谱。” “你们做…

Go语言中GC(垃圾回收回收机制)三色标记与混合写屏障

5、Golang三色标记混合写屏障GC模式全分析 (yuque.com) 第1讲-课程目标_哔哩哔哩_bilibili Golang三色标记GC混合写屏障 Go V1.3之前的标记清除&#xff08;mark and sweep) 垃圾回收、内存管理、自动适放、三色标记法、STW (stop the world) 图的遍历&#xff1f;可达性分…

路网双线合并单线——ArcGISpro 解决方法

路网双线合并成单线是一个在地图制作、交通规划以及GIS分析中常见的需求。双线路网定义&#xff1a;具有不同流向、不同平面结构的道路。此外&#xff0c;车道数较多的道路&#xff08;例如&#xff0c;双黄实线车道数大于4的道路&#xff09;也可以视为双线路网&#xff0c;本…

走,戴上你的旅行搭子去探险吧

如今快节奏的生活经常会让人感到疲惫和压力&#xff0c;炎炎夏日&#xff0c;一直想给自己松松绑&#xff01; 最近我和我的的小伙伴们去户外探险了&#xff0c;三五好友&#xff0c;组队出去玩&#xff0c;真的很让人放松&#xff01;一起去户外呼吸大自然的空气&#xff0c;…

【Hot100】LeetCode—155. 最小栈

目录 题目1- 思路2- 实现⭐155. 最小栈——题解思路 3- ACM 实现 题目 原题连接&#xff1a;155. 最小栈 1- 思路 思路 最小栈 ——> 借助两个栈来实现 2- 实现 ⭐155. 最小栈——题解思路 class MinStack {Stack<Integer> data;Stack<Integer> min;public …

VUE:跨域配置代理服务器

//在vite.config。js中&#xff0c;同插件配置同级进行配置server:{proxy:{"/myrequest":{//代理域名&#xff0c;可自行修改target:"https://m.wzj.com/",//访问服务器的目标域名changeOrigin:true,//允许跨域configure:(proxy,options) > {proxy.on(&…

图片常用的压缩方法,适用多种常用图片格式

jpg、png、jpeg、gif等图片格式是日常最常用的三种图片类型&#xff0c;一般在使用或者上传图片的时候这几种是比较常用的格式。在使用图片的时候&#xff0c;最常见的一个问题就是图片太大需要缩小后才可以正常使用&#xff0c;那么有什么方法或者工具能够快速处理不同图片格式…

在Mac上免费恢复已删除的PowerPoint文件

Microsoft PowerPoint for Mac 允许您在 macOS 环境中访问您熟悉的 PowerPoint 工具。该软件是Mac版Microsoft Office套件的一部分&#xff0c;具有各种稳定版本&#xff0c;即。PowerPoint 2019、2016、2011 等 PowerPoint for Mac 与 Apple 自己的演示应用程序 Keynote 兼容…

组网升级,双击热备和宽带管理

拓扑 要求&#xff1a; 要求12&#xff1a; 要求13&#xff1a; 要求14&#xff1a; 要求15&#xff1a; 要求16&#xff1a;

记录些MySQL题集(2)

MySQL 不使用limit的分页查询 limit问题&#xff1a;limit&#xff0c;offset递增问题。随着offset的增加&#xff0c;条数不变&#xff0c;耗时却增加了。 limit 0,10 耗时1ms limit 300000,10 耗时152ms limit 600000,10 耗时312ms 毫秒级别可能没感觉。假…

netdata 监控软件安装与学习

netdata官网 netdata操作文档 前言&#xff1a; netdata是一款监控软件&#xff0c;可以监控多台主机也可以监控单台主机&#xff0c;监控单台主机时&#xff0c;开箱即用&#xff0c;web ui很棒。 环境&#xff1a; [root192 ~]# cat /etc/system-release CentOS Linux rel…

工业大数据是什么?应用工业大数据时面临哪些挑战?

在当今快速发展的工业领域&#xff0c;大数据已成为推动企业转型升级的核心动力。工业大数据&#xff0c;以其独特的价值和潜力&#xff0c;正逐渐改变着传统的生产、管理和决策模式。然而&#xff0c;伴随着大数据的快速发展&#xff0c;一系列挑战也随之浮现。本文将深入探讨…

玻璃透过率太阳光辐射系统模拟器

太阳光模拟器概述 太阳光模拟器是一种先进的实验室设备&#xff0c;它能模拟太阳光的全光谱辐射&#xff0c;包括紫外线、可见光和红外线&#xff0c;用以评估材料、产品或设备在太阳辐射影响下的性能和耐久性。太阳光模拟器在多个领域有着广泛的应用&#xff0c;如光伏电池测…

【GD32】从零开始学GD32单片机 | WDGT看门狗定时器+独立看门狗和窗口看门狗例程(GD32F470ZGT6)

1. 简介 看门狗从本质上来说也是一个定时器&#xff0c;它是用来监测硬件或软件的故障的&#xff1b;它的工作原理大概就是开启后内部定时器会按照设置的频率更新&#xff0c;在程序运行过程中我们需不断地重装载看门狗&#xff0c;以使它不溢出&#xff1b;如果硬件或软件发生…

书生大模型实战营-入门岛-第3关

Python Python实现wordcount import string def wordcount(text):# 去除标点符号translator str.maketrans(, , string.punctuation)text text.translate(translator)# 将所有单词转换为小写text text.lower()# 将文本分割为单词列表words text.split()# 统计每个单词出现…

Webpack看这篇就够了

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

python项目读取oracle数据库方法(cx_Oracle库实现)

目录 创建一个python项目&#xff0c;并配置运行环境 查看oracle对应数据库版本&#xff08;该标题下内容只是为了查看版本&#xff0c;不用在意&#xff09; 从oracle官网下载对应版本的oracle客户端 解压下载的压缩包&#xff0c;并获取依赖 将依赖文件导入python项目运…

护眼台灯真的有用吗?一文搞懂台灯怎么选对眼睛好

现在我们很多家长对自己孩子的视力十分关心&#xff0c;生怕自己的孩子是近视、远视、弱视等等。对于父母而言&#xff0c;在孩子读书压力大课业重的关键时期&#xff0c;为孩子选择合适的学习桌椅、护眼灯从而保护孩子的眼睛是非常重要的事情!那么买给孩子学习的台灯又该注意哪…

数据库操作太复杂?Python Shelve模块让你轻松存储,一键搞定!

目录 1、基本操作入门 &#x1f4da; 1.1 安装Shelve模块 1.2 创建与打开Shelve文件 2、存储与读取数据 &#x1f510; 2.1 写入键值对 2.2 读取存储的数据 3、高级功能探索 &#x1f9ed; 3.1 使用Shelve迭代键和值 3.2 键的管理&#xff1a;添加、删除与更新 4、异…