【pthread使用】

pthread

pthread_mutex

pthread_mutex是一种基于线程的锁机制,用于保护共享资源,防止多个线程同时访问这些资源的同时进行修改。pthread_mutex的底层原理如下:

  1. 互斥锁:pthread_mutex是一种互斥锁(Mutex),多个线程不能同时占有同一把锁。线程需要等待锁被释放后才能获得锁,并进入临界区。

  2. 阻塞和解除阻塞:当锁被占用时,其他线程需要等待锁的释放。线程可以调用pthread_mutex_lock函数来请求锁。如果锁被占用,则线程会进入阻塞状态,直到锁被释放为止。而当锁的持有者调用pthread_mutex_unlock释放锁的时候,等待锁的线程会被解除阻塞,其中一个线程会获取到锁并开始执行临界区代码。

  3. 操作系统提供的同步原语:pthread_mutex是基于操作系统提供的同步原语实现的。它依赖于底层的操作系统调用,如futex等。pthread_mutex_lock函数的实现与操作系统提供的同步原语的实现紧密相连,以确保线程在等待锁的时候,CPU不会浪费,同时兼顾等待线程的快速唤醒。

  4. 优先级反转问题:pthread_mutex锁在实现中存在优先级反转问题。如果锁在高优先级进程中被持有,而低优先级进程需要等待锁,则低优先级进程无法运行,高优先级进程也因为等待其他资源而无法运行。操作系统会根据一些算法判断进程优先级,以确保线程能够快速地获得锁。

总的来说,pthread_mutex是一个由互斥锁和操作系统同步原语构成的基于线程的锁机制,用于保护共享资源的访问。它能够正确地处理优先级反转等问题,确保线程安全地访问共享资源。

pthread_mutex_init

pthread_mutex_init是一个函数,在使用线程时,它是创建线程锁的第一个步骤。线程锁是一种用于多线程编程的同步机制,它是用来保护共享资源,确保在线程要访问共享变量时,只有一个线程进行访问。使用线程锁能够有效地避免竞争条件的发生。

pthread_mutex_init函数主要是用来初始化线程锁,它的函数原型如下:

int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);

其中,mutex参数是一个指向pthread_mutex_t结构体的指针,用来指定要初始化的线程锁;attr参数是一个可选的指向pthread_mutexattr_t的指针,用来指定线程锁的属性,例如锁的类型、锁的进程共享属性等。

在使用pthread_mutex_init函数时,主要需要注意以下几点:

  1. 需要在创建线程锁之前调用该函数。

  2. 如果不需要指定锁的属性,可以将attr参数设置为NULL。

  3. 函数成功执行后,mutex所指向的pthread_mutex_t结构体将会被初始化为可用的线程锁。

使用pthread_mutex_init函数初始化的线程锁,在使用完毕后一定要记得调用pthread_mutex_destroy函数来销毁线程锁,并且需要确保所有线程都已退出该锁。

综上所述,pthread_mutex_init函数是用来初始化线程锁的重要函数,能够帮助程序员保护共享资源,提高多线程编程的效率和安全性。

pthread_mutex_init函数用于初始化互斥锁,它有两个参数:

  1. 第一个参数是指向互斥锁变量的指针,用于存储互斥锁的信息。

  2. 第二个参数是一个指向pthread_mutexattr_t类型的结构体指针,用于设置互斥锁的属性。

在不设置属性时,第二个参数可以设置为NULL。如果设置了属性,则需要使用pthread_mutexattr_init函数对属性进行初始化,并通过pthread_mutexattr_setxxx函数设置属性值,再将属性指针作为参数传递给pthread_mutex_init函数。

  • pthread_mutex_init常用的属性包括:
  1. PTHREAD_PROCESS_SHARED:互斥锁可以在多个进程间共享。

  2. PTHREAD_MUTEX_RECURSIVE:互斥锁是可重入的,同一个线程可以多次获取同一个互斥锁,释放时也要相应多次。

  3. PTHREAD_MUTEX_ERRORCHECK:互斥锁是带错误检查的,如果同一个线程多次获取同一个互斥锁,则返回错误码EBUSY。

#include <pthread.h>
pthread_mutex_t mutex;
int main() {
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
    pthread_mutex_init(&mutex, &attr);
    // 使用互斥锁
    ...
    pthread_mutex_destroy(&mutex);
    pthread_mutexattr_destroy(&attr);
    return 0;
}
  • pthread_mutex_init初始化参数详细介绍:
  1. PTHREAD_PROCESS_SHARED: 表示创建的互斥锁可以在多个进程间进行共享,即该互斥锁可以被多个进程同时占用和修改。使用该选项需要确保使用的操作系统支持进程间共享,否则将会发生错误。
  2. PTHREAD_MUTEX_RECURSIVE: 表示创建的互斥锁是可递归的,即同一线程可以多次获取该互斥锁而不会发生死锁。递归锁经常用于模块内部实现,确保模块内部的可重入性。
  3. PTHREAD_MUTEX_ERRORCHECK: 表示创建的互斥锁是错误检查的,即如果一个线程尝试多次获取同一个互斥锁,则会报错并返回一个错误码。对错误检查不够谨慎的程序可能会出现死锁问题。
  • pthread_mutex_init 使用场景如下:
  1. PTHREAD_PROCESS_SHARED:多个进程需要协同工作,同时使用共享资源时,需要使用该选项创建可进程共享的互斥锁。

  2. PTHREAD_MUTEX_RECURSIVE:在同一个线程内,需要多次获取同一个互斥锁跨内部操作的时候使用。

  3. PTHREAD_MUTEX_ERRORCHECK:在多线程应用程序的开发过程中,如果需要确保同一时间只有一个线程可以访问共享资源,但是其它线程尝试获取锁时,出现了错误,则使用该选项可以使应用程序在运行时发现这种错误情况,帮助定位调试问题。

  • pthread_mutex_init初始化的锁如果需要在多个进程间共享,应该如何设置
    pthread_mutex_init函数创建的锁只能在同一个进程中的线程间共享,如果需要在多个进程间共享锁,需要使用进程间通信(IPC)机制。其中一种方法是使用POSIX命名信号量,可以通过以下步骤实现:
  1. 使用sem_open函数创建并打开一个命名信号量,指定一个名称和权限(例如O_CREAT | O_EXCL | 0666)。

  2. 使用pthread_mutexattr_init函数初始化属性。

  3. 使用pthread_mutexattr_setpshared函数设置属性为PTHREAD_PROCESS_SHARED。

  4. 使用pthread_mutex_init函数初始化锁,并将属性作为参数传递给函数。

  5. 在需要使用锁的多个进程中,使用sem_open函数打开同一名称的命名信号量,并将返回的信号量指针作为参数传递给pthread_mutex_init函数。这样在不同进程中的锁就可以使用同一个信号量,从而实现了进程间的共享。

需要注意的是,在使用命名信号量之前应该先清除(unlink)它,以确保该名称尚未被使用。此外,在使用命名信号量时要格外小心,因为信号量是全局共享的。

  • pthread_mutex_init初始化的锁为NULL,而不是PTHREAD_PROCESS_SHARED,可以在多个进程间共享吗
    不可以。如果使用pthread_mutex_init初始化的锁为NULL,则表示创建的是一个进程内的锁(也称为“进程内锁”或“线程锁”),只能在同一个进程内的不同线程之间使用,无法在多个进程之间共享。要在多个进程之间共享锁,需要使用pthread_mutex_initPTHREAD_PROCESS_SHARED参数创建一个“进程间锁”。

pthread_mutex_lock

pthread_mutex_lock函数是POSIX线程库中的一种互斥锁,用于保护共享资源,防止并发访问。这个函数尝试获取互斥锁,如果锁已经被锁住了,则线程会进行阻塞等待锁被释放。

pthread使用示例

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t lock;
void* thread_function(void* arg) {
    // 获取互斥锁
    pthread_mutex_lock(&lock);
    
    // 现在我们得到了锁,可以安全地访问共享资源
    int* p_num = (int*)arg;
    printf("Thread %d is accessing shared resource...\n", *p_num);
    for(int i = 0; i < 5; i++) {
        printf("Thread %d: %d\n", *p_num, i);
        sleep(1);
    }
    printf("Thread %d finished accessing shared resource.\n", *p_num);
    
    // 释放互斥锁
    pthread_mutex_unlock(&lock);
    return NULL;
}
int main() {
    // 初始化互斥锁
    pthread_mutex_init(&lock, NULL);
    
    // 创建两个线程
    pthread_t thread1, thread2;
    int num1 = 1, num2 = 2;
    pthread_create(&thread1, NULL, thread_function, &num1);
    pthread_create(&thread2, NULL, thread_function, &num2);
    
    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    // 销毁互斥锁
    pthread_mutex_destroy(&lock);
    return 0;
}

在上面的示例程序中,创建了两个线程,它们都会访问共享资源。在访问共享资源之前,线程会通过pthread_mutex_lock函数获取互斥锁。如果锁已经被另一个线程锁住了,当前线程就会阻塞,直到锁被释放。

当一个线程使用完共享资源之后,会通过pthread_mutex_unlock函数释放互斥锁,以便其他线程可以访问共享资源。在程序结束时,还要通过pthread_mutex_destroy函数销毁互斥锁。

pthread共享锁使用示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#define SHARED_MEM_NAME "/my_shared_mem"
#define SHARED_MEM_SIZE 1024
pthread_mutex_t *mutex;
void *thread_func(void *arg) {
    int id = *(int*)arg;
    while (1) {
        pthread_mutex_lock(mutex);
        printf("Thread %d has entered the critical section.\n", id);
        sleep(1);
        printf("Thread %d is leaving the critical section.\n", id);
        pthread_mutex_unlock(mutex);
        sleep(2);
    }
    return NULL;
}
int main() {
    int i, fd, *ids;
    pthread_t *threads;
    struct stat buf;
    // 创建或打开共享内存
    fd = shm_open(SHARED_MEM_NAME, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("shm_open error");
        exit(EXIT_FAILURE);
    }
    if (ftruncate(fd, SHARED_MEM_SIZE) == -1) {
        perror("ftruncate error");
        exit(EXIT_FAILURE);
    }
    if (stat(SHARED_MEM_NAME, &buf) == -1) {
        perror("stat error");
        exit(EXIT_FAILURE);
    }
    // 将共享内存映射到进程的地址空间
    mutex = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mutex == MAP_FAILED) {
        perror("mmap error");
        exit(EXIT_FAILURE);
    }
    // 初始化互斥锁
    pthread_mutexattr_t mutex_attr;
    pthread_mutexattr_init(&mutex_attr);
    pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(mutex, &mutex_attr);
    // 创建多个线程
    threads = malloc(sizeof(pthread_t) * 3);
    ids = malloc(sizeof(int) * 3);
    for (i = 0; i < 3; i++) {
        ids[i] = i + 1;
        pthread_create(&threads[i], NULL, thread_func, &ids[i]);
    }
    // 等待线程完成
    for (i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    // 销毁互斥锁
    pthread_mutex_destroy(mutex);
    pthread_mutexattr_destroy(&mutex_attr);
    // 取消映射共享内存
    if (munmap(mutex, SHARED_MEM_SIZE) == -1) {
        perror("munmap error");
        exit(EXIT_FAILURE);
    }
    // 删除共享内存
    if (shm_unlink(SHARED_MEM_NAME) == -1) {
        perror("shm_unlink error");
        exit(EXIT_FAILURE);
    }
    return 0;
}

此示例创建三个线程,在其中一个线程运行时,它会获得互斥锁并进入临界区。在两个其他线程的其他并行运行期间,它们无法进入临界区,因为该锁已被占用。当第一个线程完成并释放锁时,其他线程可以获得互斥锁并进入临界区以执行其任务。在这个示例中,所有线程都在打印一些消息后离开临界区。

请注意,示例中使用了pthread_mutexattr_setpshared函数设置了互斥锁属性,以便在多进程环境下使用。此属性允许进程之间共享互斥锁,并且必须在互斥锁初始化之前设置。此外,共享内存的大小预定义为1024字节,可以根据需要进行调整。

pthread+信号量实现共享线程锁

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM_THREADS 5
// 共享变量
int shared_variable = 0;
sem_t shared_variable_lock;  // 共享变量的非负整数信号量
void *thread_function(void *thread_id) {
    int thread_num = *(int*)thread_id;
    int i, tmp;
    
    // 每个线程加锁,修改共享变量,再解锁
    for (i = 0; i < 100000; i++) {
        sem_wait(&shared_variable_lock);
        
        tmp = shared_variable;
        tmp++;
        shared_variable = tmp;
        
        sem_post(&shared_variable_lock);
    }
    
    printf("Thread %d finished. Shared variable: %d\n", thread_num, shared_variable);
    pthread_exit(NULL);
}
int main(int argc, char *argv[]) {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];
    int rc, t;
    
    // 初始化非负整数信号量为 1
    sem_init(&shared_variable_lock, 0, 1);
    
    // 创建多个线程
    for (t = 0; t < NUM_THREADS; t++) {
        thread_ids[t] = t;
        rc = pthread_create(&threads[t], NULL, thread_function, (void*)&thread_ids[t]);
        if (rc) {
            printf("Error: return code for pthread_create() is %d\n", rc);
            exit(-1);
        }
    }
    
    // 等待所有线程结束
    for (t = 0; t < NUM_THREADS; t++) {
        pthread_join(threads[t], NULL);
    }
    
    // 销毁信号量并退出
    sem_destroy(&shared_variable_lock);
    pthread_exit(NULL);
}

该示例创建了 5 个线程,每个线程向共享变量中增加 100000。线程间通过非负整数信号量来同步访问共享变量,保证了线程安全。最终输出共享变量的值。

Semaphore(信号量)实现进程间同步操作

Semaphore(信号量)是一种用于进程间同步和互斥的机制,其主要目的是为了防止多个进程同时访问共享资源而造成竞争,从而导致数据不一致的情况发生。Semaphore可以被用于任何需要同步进程的场景,比如保证文件系统中的文件不被多个进程同时访问等等。

在Linux系统中,Semaphore被定义为一种系统资源,每个进程可以通过semget系统调用来获取一个或多个Semaphore的ID,从而可以使用这些Semaphores在不同的进程之间进行同步。如下所示是使用semget函数来获取Semaphore ID的例子:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[])
{
    key_t key;
    int semid;
    if ((key = ftok(".", 'S')) == -1) {
        perror("ftok");
        exit(1);
    }
    if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) {
        perror("semget");
        exit(1);
    }
    printf("Semaphore ID : %d\n", semid);
    return 0;
}

在上面的例子中,我们首先使用ftok函数创建一个key值,然后使用semget函数来获取一个Semaphore ID。其中key是用来唯一标识Semaphore的,同时也是用来创建Semaphore的。如果Semaphore已经存在,则直接获取Semaphore的ID,如果尚未存在,则会创建一个新的Semaphore。该函数的第一个参数是key值,第二个参数是创建Semaphore的数量,第三个参数是指定Semaphore的权限。

得到Semaphore ID之后,我们可以使用semctl函数来控制Semaphore的行为。 它可以用来设置Semaphore的值,根据需要进行等待/通知,以及删除Semaphore等等。比如下面是一个简单的Semaphore使用的例子,其中使用了semctl函数来实现了两个进程之间的同步:


#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define SEM_VALUE 1
int main(int argc, char *argv[])
{
    key_t key;
    int semid, sem_val;
    struct sembuf buf;
    if ((key = ftok(".", 'S')) == -1) {
        perror("ftok");
        exit(1);
    }
    if ((semid = semget(key, 1, 0666 | IPC_CREAT)) == -1) {
        perror("semget");
        exit(1);
    }
    if (semctl(semid, 0, SETVAL, SEM_VALUE) == -1) {
        perror("semctl");
        exit(1);
    }
    if (fork() == -1) {
        perror("fork");
        exit(1);
    }
    buf.sem_num = 0;
    buf.sem_op = -1;
    buf.sem_flg = SEM_UNDO;
    if (semop(semid, &buf, 1) == -1) {
        perror("semop");
        exit(1);
    }
    printf("Child process is running now.\n");
    buf.sem_op = 1;
    if (semop(semid, &buf, 1) == -1) {
        perror("semop");
        exit(1);
    }
    printf("Child process completed.\n");
    if (wait(NULL) == -1) {
        perror("wait");
        exit(1);
    }
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("semctl");
        exit(1);
    }
    return 0;
}

在上面的例子中,我们首先使用semctl函数来将Semaphore的初始值设置为1。然后使用fork函数创建了一个子进程,并使用Semaphore来同步它们之间的运行。首先子进程执行wait操作并进入阻塞状态,此时父进程恰好通过执行完上面的wait操作。 然后父进程执行一些操作,并调用semop函数增加Semaphore的值,这样就使得子进程不再被阻塞。最后各个进程完成它们的操作后,Semaphore被删除,释放它所使用的系统资源。

以上是一个简单的Semaphore同步操作,使用semget,semctl和semop函数,我们可以利用Semaphore实现进程间同步和互斥操作。

pthread和shmget来实现共享锁

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
pthread_mutex_t *mutex;
void *worker(void *arg) {
    pthread_mutex_lock(mutex);
    printf("Thread %ld gets the lock!\n", (long) arg);
    sleep(2);
    printf("Thread %ld releases the lock!\n", (long) arg);
    pthread_mutex_unlock(mutex);
    return NULL;
}
int main(int argc, char *argv[]) {
    int shmid;
    key_t key = 1234;
    if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    if ((mutex = shmat(shmid, NULL, 0)) == (pthread_mutex_t *) -1) {
        perror("shmat");
        exit(1);
    }
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(mutex, &attr);
    pthread_t threads[5];
    int i;
    for (i = 0; i < 5; i++) {
        pthread_create(&threads[i], NULL, worker, (void *) i);
    }
    for (i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }
    pthread_mutex_destroy(mutex);
    shmdt(mutex);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

在这个示例中,我们使用了pthread和shmget来实现共享锁。主线程首先创建一个共享内存,并将其连接到一个pthread_mutex_t类型的指针变量mutex中。然后,我们使用pthread_mutexattr_t结构来给这个互斥锁设置了PTHREAD_PROCESS_SHARED属性,这样我们就可以使用它了。接下来我们创建了5个线程,并使用pthread_mutex_lock和pthread_mutex_unlock函数来管理这个共享锁。

shmget和shm_open区别

shmget和shm_open都是用来创建共享内存的函数,不过它们有以下几点区别:

  1. POSIX vs System V

shmget是System V IPC(Inter-Process Communication)机制的一部分,而shm_open是POSIX共享内存机制的一部分。POSIX在开放系统中广泛使用,因此shm_open在现代系统上更常见。

  1. 名称

shmget使用一个整数键来标识共享内存段,而shm_open使用一个字符串名称来标识共享内存对象。这意味着shm_open更容易使用和识别,因为名称可能会更有描述性,而不仅仅是一个数字。

  1. 文件句柄

一旦创建了共享内存对象,shm_open将返回一个文件句柄,该文件句柄可以像其他文件一样使用open(), read(), write()等函数。而shmget则只返回一个共享内存ID。

  1. 访问控制

shm_open提供了更好的权限控制,因为它允许您指定共享内存是否应该对其他用户或组可见,而shmget则没有这种级别的控制。

  1. 其它

shm_open还允许您将共享内存映射到进程的地址空间中,以便更方便地访问共享内存。此外,shm_open还支持文件锁定和文件映射的内存同步机制。

综上所述,虽然shmget和shm_open都可以用来创建共享内存,但它们有自己的优点和不同的用途。在现代系统中,shm_open更常见,因为它更容易使用和控制,而且还提供了更多的功能。

  • 详细介绍
  1. 用途不同

shmget主要用于创建和获取一个共享内存区域,但是它没有提供文件系统和文件描述符的支持。在使用shmget时,必须使用IPC_KEY作为唯一的标识符来引用共享内存区域。

shm_open主要用于在文件系统中创建或打开一个共享内存对象,它可以使用文件描述符来引用共享内存对象,支持多个进程同时访问。shm_open创建的共享内存对象可以从文件系统中删除,也可以与其他文件描述符共享。

  1. 调用方式不同

shmget是一个系统调用函数,需要使用IPC_KEY作为参数,返回一个共享内存的标识符。

shm_open是一个库函数,需要指定一个文件名和标志位作为参数,返回一个文件描述符。与shmget不同的是,shm_open的文件名只是文件系统中的一个虚拟的名字,不需要实际的物理文件支持。

  1. 参数设置不同

在使用shmget时,必须指定共享内存的大小和权限,而在shm_open中,共享内存的大小是通过ftruncate函数设置的。

此外,shm_open还支持一些特殊的标志位,如O_CREAT表示如果该共享内存对象不存在则创建,O_EXCL表示如果共享内存对象已经存在则出错,O_RDWR表示以读写模式打开共享内存对象等。

综合来看,shmget和shm_open都是用于创建共享内存的函数,但是它们在具体用途、调用方式和参数设置方面存在很大的差异。开发者应根据实际需求选择恰当的函数。

thread

pthread是一种POSIX线程库,是一种C语言的多线程编程接口,可以在Linux、Unix、Mac OS等操作系统中使用。而thread是C++11标准中新增加的多线程编程库,是C++语言的多线程编程接口。其区别在于:

  1. 语言支持:pthread是C语言的线程库,而thread是C++的线程库。

  2. 功能差异:pthread提供了更底层的线程操作函数,如pthread_create()、pthread_join()等,需要程序员手动管理线程的状态和资源。而thread库提供了更高层次的抽象,如std::thread类,它封装了线程的创建、销毁和等待等操作,使用起来更加方便,避免了手动管理线程状态和资源的繁琐操作。

  3. 可移植性:pthread是POSIX标准的一部分,可以在各种POSIX兼容的系统上使用。而thread是C++11标准中新增加的特性,需要编译器支持C++11才能使用,在一些老的编译器或操作系统上可能不兼容。

综上所述,pthread更加底层,需要手动管理线程状态和资源,而thread提供了更高层次的抽象,使用更加方便,但需要编译器支持C++11。

  • C++11的std::mutex可以使用shm_open打开,从而实现共享锁吗
    不可以。C++11的std::mutex是一个内存中的对象,无法通过shm_open打开。shm_open打开的是共享内存区域,它只能用于在进程之间共享内存数据。如果需要在进程之间共享锁,可以使用POSIX的信号量机制。
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自动驾驶小哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值