20242817李臻《Linux⾼级编程实践》第7周

news2025/5/29 7:01:20

20242817李臻《Linux⾼级编程实践》第7周

一、AI对学习内容的总结

第八章:多线程编程

8.1 多线程概念

  • 进程与线程的区别
    • 进程是资源分配单位,拥有独立的地址空间、全局变量、打开的文件等。
    • 线程是调度单位,在同一进程内的线程共享这些资源,通信方便,切换代价小。
  • 线程特点
    • 调度资源少,响应快,占用资源少。
    • 同一进程内线程共享资源,通信便捷,无需复杂通信机制。
    • 提高程序响应时间,适合图形界面等需要及时响应用户操作的场景。
    • 提高CPU利用率,尤其在多CPU环境下,线程可并行执行。
  • Linux线程
    • 遵循POSIX线程接口(pthread),用户级线程,在用户空间调度。
    • 线程的创建、同步、销毁等操作通过pthread库函数实现。

8.2 线程状态与线程编程

8.2.1 线程的创建和参数传递

  • pthread_create:创建新线程。
    • 参数包括线程ID指针、属性、执行代码函数指针、传递参数。
    • 示例代码展示了如何创建线程并传递参数。
  • 线程参数传递:通过pthread_createarg参数传递给线程函数。

8.2.2 线程终止

  • pthread_exit:线程正常终止,可返回值给pthread_join
  • exit:导致整个进程结束,需谨慎使用。
  • 资源清理pthread_cleanup_pushpthread_cleanup_pop用于线程终止时的资源清理。

8.2.3 线程挂起

  • pthread_join:等待指定线程结束,实现线程同步。
    • 一个线程不能被多个线程执行pthread_join操作。

8.2.4 线程其他相关系统调用

  • 线程标识pthread_self获取线程ID。
  • 线程ID与进程ID:线程与主进程共享进程ID,但线程ID不同。

8.3 线程的同步与互斥

8.3.1 互斥量

  • 互斥量作用:防止多线程并发访问共享资源。
  • 互斥量特性
    • 原子性:操作要么全部完成,要么一个也不执行。
    • 唯一性:同一时间只有一个线程能锁定互斥量。
    • 非繁忙等待:线程等待时不会占用CPU资源。
  • 互斥量使用
    • 声明和初始化:静态PTHREAD_MUTEX_INITIALIZER或动态pthread_mutex_init
    • 加锁:pthread_mutex_lock
    • 判断加锁:pthread_mutex_trylock
    • 解锁:pthread_mutex_unlock
    • 销毁:pthread_mutex_destroy

8.3.2 信号量

  • 信号量作用:用于线程同步与互斥,基于P/V操作。
  • 信号量使用
    • 初始化:sem_init
    • P操作:sem_wait
    • V操作:sem_post
    • 销毁:sem_destroy
  • 信号量类型
    • 用于线程互斥:单个信号量。
    • 用于线程同步:多个信号量。

8.3.3 条件变量

  • 条件变量作用:等待特定条件发生,与互斥锁结合使用。
  • 条件变量使用
    • 初始化:pthread_cond_init
    • 等待:pthread_cond_waitpthread_cond_timedwait
    • 信号:pthread_cond_signal
    • 广播:pthread_cond_broadcast
    • 销毁:pthread_cond_destroy

示例代码

线程创建与参数传递

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

typedef struct student {
    int age;
    char name[20];
} STU;

void* create(void* arg) {
    STU* temp = (STU*)arg;
    printf("The following is transferred to thread\n");
    printf("stu age is %d\n", temp->age);
    printf("stu name is %s\n", temp->name);
    return NULL;
}

int main(int argc, char argv[]) {
    pthread_t tid;
    int error;
    STU* stu = malloc(sizeof(STU));
    stu->age = 20;
    strcpy(stu->name, "abcdefg");
    error = pthread_create(&tid, NULL, create, (void*)stu);
    if (error != 0) {
        printf("pthread_create failed\n");
        return -1;
    }
    pthread_join(tid, NULL);
    return 0;
}

多线程共享变量

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

static int global = 1;

void* execute(void* arg) {
    while (global < 100) {
        printf("The value is %d\n", global);
        sleep(1);
    }
    return NULL;
}

void* t2_execute(void* arg) {
    while (global < 100) {
        global++;
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t pid1, pid2;
    int error1, error2;
    error1 = pthread_create(&pid1, NULL, execute, NULL);
    error2 = pthread_create(&pid2, NULL, t2_execute, NULL);
    if (error1 != 0 || error2 != 0) {
        printf("pthread_create failed\n");
        return -1;
    }
    pthread_join(pid1, NULL);
    pthread_join(pid2, NULL);
    return 0;
}

线程终止与清理

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

void* t1_execute(void* arg) {
    while (1) {
        printf("in thread1\n");
    }
}

void* t2_execute(void* arg) {
    sleep(2);
    pthread_exit(NULL); // 使用exit(0)对比
}

int main() {
    pthread_t pid1, pid2;
    int error1, error2;
    error1 = pthread_create(&pid1, NULL, t1_execute, NULL);
    error2 = pthread_create(&pid2, NULL, t2_execute, NULL);
    if (error1 != 0 || error2 != 0) {
        printf("pthread_create failed\n");
        return -1;
    }
    pthread_join(pid1, NULL);
    pthread_join(pid2, NULL);
    return 0;
}

线程同步

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

void* t1_exe(void* arg) {
    int i;
    for (i = 1; i <= 5; i++) {
        printf("%d\n", i);
        fflush(stdout);
    }
    return NULL;
}

void* t2_exe(void* arg) {
    pthread_t tid = (pthread_t)arg;
    pthread_join(tid, NULL);
    int i;
    for (i = 6; i <= 10; i++) {
        printf("%d\n", i);
    }
    return NULL;
}

int main() {
    pthread_t pid1, pid2;
    int error1, error2;
    error1 = pthread_create(&pid1, NULL, t1_exe, NULL);
    error2 = pthread_create(&pid2, NULL, t2_exe, (void*)pid1);
    if (error1 != 0 || error2 != 0) {
        printf("pthread_create failed\n");
        return -1;
    }
    pthread_join(pid2, NULL);
    return 0;
}

互斥量使用

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

char str[] = "abcdefghijklmnopqrstuvwxyz123456789";
pthread_mutex_t mutex;
int index2 = 0;

void* t1_exe(void* arg) {
    while (index2 < strlen(str) - 1) {
        pthread_mutex_lock(&mutex);
        printf("The %dth element of array is %c\n", index2, str[index2]);
        sleep(1);
        index2++;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t pid1, pid2;
    int error1, error2;
    pthread_mutex_init(&mutex, NULL);
    error1 = pthread_create(&pid1, NULL, t1_exe, NULL);
    error2 = pthread_create(&pid2, NULL, t1_exe, NULL);
    if (error1 != 0 || error2 != 0) {
        printf("pthread_create failed\n");
        return -1;
    }
    pthread_join(pid1, NULL);
    pthread_join(pid2, NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}

信号量使用

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

#define MAX 100
static sem_t sem;
static int global = 1;

void* t1_exe(void* arg) {
    while (global < MAX) {
        sem_wait(&sem);
        printf("In thread1 before increment global=%d\n", global);
        global++;
        printf("In thread1 after increment global=%d\n", global);
        sem_post(&sem);
        sleep(5);
    }
    return NULL;
}

void* t2_exe(void* arg) {
    while (global < MAX) {
        sem_wait(&sem);
        printf("In thread2 before double global=%d\n", global);
        global *= 2;
        printf("In thread2 after double global=%d\n", global);
        sem_post(&sem);
        sleep(6);
    }
    return NULL;
}

int main() {
    pthread_t pid1, pid2;
    int error1, error2;
    if (sem_init(&sem, 0, 1) == -1) {
        perror("sem_init failed");
        exit(0);
    }
    error1 = pthread_create(&pid1, NULL, t1_exe, NULL);
    error2 = pthread_create(&pid2, NULL, t2_exe, NULL);
    if (error1 != 0 || error2 != 0) {
        printf("pthread_create failed\n");
        return -1;
    }
    pthread_join(pid1, NULL);
    pthread_join(pid2, NULL);
    sem_destroy(&sem);
    return 0;
}

条件变量使用

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

#define MAX 100
pthread_mutex_t mutex;
pthread_cond_t cond;
int i = 1;

void* thread1(void* flag) {
    for (i = 1; i <= MAX; i++) {
        pthread_mutex_lock(&mutex);
        if (i % 9 != 0) {
            pthread_cond_signal(&cond);
        } else {
            printf("In thread1: %d\n", i);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* thread2(void* flag) {
    while (i <= MAX) {
        pthread_mutex_lock(&mutex);
        printf("In thread2 before wait\n");
        if (i % 9 == 0) {
            pthread_cond_wait(&cond, &mutex);
            printf("In thread2 after wait\n");
            printf("In thread2: %d\n", i);
        }
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

int main(void) {
    pthread_t t_a, t_b;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    pthread_create(&t_a, NULL, thread2, NULL);
    pthread_create(&t_b, NULL, thread1, NULL);
    pthread_join(t_b, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    exit(0);
}

总结

  • 线程与进程:线程是轻量级进程,共享进程资源,提高程序效率和响应性。
  • 线程生命周期管理:掌握线程创建、终止、同步、互斥等系统调用。
  • 同步与互机制
    • 互斥量:保护共享资源,防止并发访问冲突。
    • 信号量:用于资源的互斥和同步访问。
    • 条件变量:等待特定条件发生,与互斥锁结合使用。
  • 合理选择机制:根据应用程序需求,选择适当的同步与互机制,保护共享资源一致性。

二、 对AI总结的反思与补充

反思

1. 内容的全面性和准确性

原文说Linux线程是"用户级线程",这是不准确的。Linux使用的是NPTL(Native POSIX Thread Library),实现了1:1的线程模型(内核级线程),而非纯用户级线程。用户级线程(如旧版LinuxThreads)已被淘汰。

2. 线程终止示例中的潜在问题

t1_execute函数中的无限循环(while(1))没有退出条件,可能导致线程无法正常终止。建议增加终止条件或使用pthread_cancel来演示线程取消。

3. 信号量初始化检查

sem_init的返回值应明确检查是否为-1(而不是隐式与-1比较),并处理错误:

if (sem_init(&sem, 0, 1) == -1) {
    perror("sem_init failed");
    exit(EXIT_FAILURE);  // 建议用EXIT_FAILURE而非0
}

4. 条件变量示例的潜在竞态条件

thread2中检查i % 9 == 0后进入等待,但可能在pthread_cond_wait之前thread1已经修改了i,导致丢失信号。更安全的做法是将条件判断放入循环中:

while (i % 9 != 0) {
    pthread_cond_wait(&cond, &mutex);
}

补充

1. 线程安全与可重入函数

  • 线程安全函数:在多线程环境中可以安全调用的函数,通常通过互斥锁保护共享数据。
    • 示例:rand_r() 是线程安全的,而 rand() 不是线程安全的。
  • 可重入函数:不依赖静态数据或全局变量,可被多个线程同时调用而不会冲突。
    • 示例:strtok_r()strtok() 的可重入版本。

2. 线程属性

  • 线程属性设置:通过 pthread_attr_t 可以设置线程的栈大小、调度策略、分离状态等。
    • 示例代码:
      pthread_attr_t attr;
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置为分离线程
      pthread_create(&tid, &attr, thread_func, NULL);
      pthread_attr_destroy(&attr);
      
  • 分离线程:分离线程(PTHREAD_CREATE_DETACHED)退出时资源自动回收,无需调用 pthread_join()

3. 线程局部存储(TLS)

  • thread_local 关键字:C11 标准支持,每个线程拥有独立的变量副本。
    • 示例:
      thread_local int tls_variable = 0;
      
  • pthread_key_create:POSIX 接口实现线程局部存储。
    • 示例代码:
      pthread_key_t key;
      pthread_key_create(&key, NULL);
      pthread_setspecific(key, ptr); // 设置线程私有数据
      void* ptr = pthread_getspecific(key); // 获取数据
      

4. 死锁与避免策略

  • 死锁条件
    • 互斥:资源被独占使用。
    • 持有并等待:线程持有资源的同时等待其他资源。
    • 非抢占:资源不能被强制释放。
    • 循环等待:形成资源等待环路。
  • 避免方法
    • 按固定顺序获取锁。
    • 使用 pthread_mutex_trylock 避免阻塞。
    • 设置锁超时(如 pthread_mutex_timedlock)。

三、学习思维导图

第八章

在这里插入图片描述

mindmap
  root((多线程编程))
    thread_concept((线程概念))
      thread_vs_process((线程与进程的关系))
        进程是资源分配单位,线程是调度单位
        线程共享进程的内存空间(代码、全局变量、堆栈)
        同一进程内的线程通信方便
      thread_features((线程特点))
        轻量级进程(创建和切换代价小)
        调度资源少,响应快
        提高程序响应(如 GUI 应用)
        提高 CPU 利用率(多核支持)
    thread_life_cycle((线程生命周期))
      thread_creation((线程的创建))
        pthread_create
          参数:线程 ID 指针、属性、线程函数、参数
          示例:创建线程并传递结构体参数
        线程参数传递
          通过 void* 类型传递任意数据
          示例:传递学生信息结构体
      thread_termination((线程的终止))
        pthread_exit
          用于正常终止线程
          返回值传递给 pthread_join
        return 语句
          线程函数执行完毕后自然终止
        exit 语句
          结束整个进程(所有线程)
          示例:exit 和 pthread_exit 的区别
      thread_management((线程管理))
        pthread_join
          等待线程结束
          示例:线程 2 等待线程 1 打印 1-5 后打印 6-10
        pthread_self
          获取当前线程 ID
          示例:打印线程 ID 和进程 ID
        线程资源清理
          pthread_cleanup_push/pop
          示例:线程结束时执行清理函数
      thread_example((线程共享变量))
        示例:两个线程共享全局变量
          线程 1 打印变量值
          线程 2 修改变量值
    synchronization((线程同步与互斥))
      mutex((互斥量))
        互斥量的初始化与销毁
          静态初始化:PTHREAD_MUTEX_INITIALIZER
          动态初始化:pthread_mutex_init
        互斥量的加锁与解锁
          pthread_mutex_lock
          pthread_mutex_unlock
        互斥量的属性
          普通锁、嵌套锁、检错锁、适应锁
        示例:防止共享变量访问冲突
      semaphore((信号量))
        信号量的初始化与销毁
          sem_init 和 sem_destroy
        信号量的 P/V 操作
          sem_wait(P 操作)
          sem_post(V 操作)
        示例:线程互斥与同步
          互斥:两个线程交替修改全局变量
          同步:线程 1 和线程 2 轮流执行
      condition_variable((条件变量))
        条件变量的初始化与销毁
          pthread_cond_init 和 pthread_cond_destroy
        条件变量的等待与唤醒
          pthread_cond_wait
          pthread_cond_signal
        示例:线程等待特定条件
          线程 1 增加全局变量
          线程 2 等待并打印 9 的倍数
    examples((实例应用))
      shared_variable_example((共享变量访问))
        无互斥量导致冲突
        使用互斥量解决冲突
      producer_consumer_example((生产者-消费者问题))
        缓冲区大小为 100
        2 个生产者随机生成字符
        5 个消费者读取并打印
        使用信号量实现同步
      ordered_output_example((按顺序输出))
        3 个线程分别打印 A、B、C
        要求输出顺序为 ABCABC...
        使用条件变量或信号量实现
    summary((总结))
      key_takeaways((关键收获))
        线程提高并发性,但需防止资源冲突
        互斥量适合资源互斥访问
        信号量适合资源互斥和同步
        条件变量适合等待事件发生
        根据需求选择合适的同步机制
      best_practices((最佳实践))
        避免死锁(如加锁顺序一致)
        释放资源(销毁互斥量、信号量)
        调试多线程程序时注意竞态条件

四、基于AI的学习

1、关于线程挂起

  • 我:
    我在学习Linux线程挂起知识点,请你以苏格拉底的方式对我进行提问,一次一个问题

  • AI:
    好的,我们开始吧。那么,请你先告诉我,你目前对Linux线程挂起的概念有什么初步的理解呢?

  • 我:
    Linux线程挂起指的是暂时暂停线程的执行,通常通过信号(如SIGSTOP)或同步机制(如条件变量)实现,之后可用SIGCONT或条件触发恢复执行。

  • AI:
    那么,你认为在什么情况下,线程挂起会是一个有用的策略呢?

  • 我:
    线程挂起在需要暂停任务执行以等待资源、同步其他线程或节省CPU资源时很有用,比如I/O阻塞、任务调度或调试场景。

  • AI:
    很好,那么请你思考一下,线程挂起和线程阻塞有什么区别呢?

  • 我:
    线程挂起是主动或由外部暂停线程执行(如调用pause()或收到SIGSTOP信号),通常需要外部干预恢复;线程阻塞是线程因等待资源(如锁、I/O)主动进入等待状态,条件满足后自动恢复。挂起更偏向强制暂停,阻塞更多是同步逻辑的自我等待。

  • AI:
    非常棒!那么,你认为在Linux中,线程挂起和线程终止有什么本质的不同呢?

  • 我:
    线程挂起是暂停执行(可恢复),线程终止是彻底结束执行(不可恢复)。挂起保留线程上下文,终止释放所有资源。本质区别在于生命周期是否可继续。

  • AI:
    非常棒!那么,你认为在实际开发中,使用线程挂起功能时,需要特别注意哪些潜在问题呢?

  • 我:
    你的理解是什么?


  • AI:
    在实际开发中,使用线程挂起功能时,需要特别注意以下潜在问题:
  1. 死锁风险:如果线程在持有资源(如锁)时被挂起,可能会导致其他线程因无法获取资源而陷入死锁。
  2. 资源泄漏:线程挂起后可能不会释放某些资源(如动态分配的内存),需要确保在恢复执行时能够正确管理这些资源。
  3. 线程状态同步:挂起和恢复线程时,需要确保线程状态的同步,避免出现不可预期的行为。
  4. 信号处理:如果使用信号(如SIGSTOP)挂起线程,需要确保信号处理机制不会与其他信号处理逻辑冲突。
  5. 线程恢复时机:线程恢复执行的时机需要谨慎设计,避免在不合适的时刻恢复线程,导致系统不稳定或数据不一致。

  • 我:
    针对我该知识点,我理解了吗?我的回答结束了,请对我的回答进行评价总结。

  • AI:
    好的,让我们来总结一下你对Linux线程挂起知识点的理解。

从你的回答来看,你对线程挂起的基本概念、应用场景、与其他相关概念(如线程阻塞、线程终止、信号和条件变量)的对比,以及实际开发中需要注意的潜在问题都有较为清晰的认识。你能够准确地描述线程挂起的功能、实现方式以及与其他机制的区别,并且能够结合实际开发场景思考可能遇到的问题,这表明你对这个知识点有较为深入的理解。

不过,还可以进一步完善的地方是,对于线程挂起的实现细节(如具体函数调用、内核层面的处理机制等)可以再深入研究一下。例如,pause() 函数和 pthread_suspend()(如果存在)的内部实现机制,以及它们在不同Linux发行版和内核版本中的行为差异等。此外,对于线程挂起的性能影响(如上下文切换的开销)也可以进一步探讨。

总体来说,你对Linux线程挂起的知识点已经有了很好的掌握,继续保持这种学习态度,你会在这个领域取得更大的进步!

五、基于AI的代码实践

使用Gitee进行代码托管

仓库链接:

https://gitee.com/li-zhen1215/homework/tree/master/Week7

在这里插入图片描述

例1

thread_creat.c

/*thread_creat.c*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct student
{
    int age;
    char name[20];
} STU;

void *create(void *arg) { //线程将要执行的代码
    STU *temp = (STU *)arg;
    printf("The following is transferred to thread\n");
    printf("STU age is %d\n", temp->age);
    printf("STU name is %s\n", temp->name);
}

int main(int argc, char *argv[])
{
    pthread_t tidp;
    int error;
    STU *stu = malloc(sizeof(STU));
    stu->age = 20;
    strcpy(stu->name, "abcdefg");
    error = pthread_create(&tidp, NULL, create, (void *)stu);
    if (error != 0) {
        printf("pthread_create failed ");
        return -1;
    }
    pthread_join(tidp, NULL);
    return 0;
}

实践截图
在这里插入图片描述

例2

multithread.c

/*multithread.c*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h> // 添加这一行
static int global=1;

void *t1_execute(void *arg) { //线程1执行的代码
    while(global<100) {
        printf("The value is %d\n",global);
    }
}

void *t2_execute(void *arg) { //线程2执行的代码
    while (1) {
        global++;    //改变进程变量 global 的值
        sleep(1);    // 暂停1秒
    }
}

int main() { // main 函数应该返回 int 类型
    int i=0;
    pthread_t pid1,pid2;
    int error1,error2;
    error1=pthread_create(&pid1,NULL, t1_execute,(void *)&i);
    error2=pthread_create(&pid2,NULL, t2_execute,(void *)&i);
    if(error1!=0||error2!=0) {
        printf("pthread_create failed ");
        return 1; // 返回非零值表示错误
    }
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    return 0; // 返回零值表示成功
}

实践截图
在这里插入图片描述

在这里插入图片描述

例3

multhread.c

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 添加这一行以包含 sleep 函数的声明

void *t1_execute(void *arg){
    while(1) {
        printf("in thread1\n");
    }
}

void *t2_execute(void *arg){
    sleep(2);
    pthread_exit(NULL); // 用 exit(0); 语句替换
}

int main(){ // 修改 main 函数的返回类型为 int
    pthread_t pid1,pid2;
    int error1,error2;
    error1=pthread_create(&pid1,NULL, t1_execute,NULL);
    error2=pthread_create(&pid2,NULL, t2_execute, NULL);

    if(error1!=0||error2!=0) {
        printf("pthread_create failed ");
        return -1; // 保留 return -1;
    }
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    return 0; // 保留 return 0;
}

实践截图

在这里插入图片描述
在这里插入图片描述

例4

/*mulfunc.c*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void clean_1(void *arg){
    printf("%s\n", (char *)arg);
}

void *t1_execute(void *arg){
    pthread_cleanup_push(clean_1,"thread first handler");
    pthread_cleanup_push(clean_1,"thread second handler");
    if ( *(int*)arg==1 ) pthread_exit(NULL);
    else exit(0);
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
}

void main(){
    pthread_t pid1;
    int error1;
    int i=1;
    error1=pthread_create(&pid1,NULL, t1_execute,(void *)&i);
    if(error1!=0) {
        printf("pthread_create failed ");
        return ;
    }
    pthread_join(pid1,NULL);
    return ;
}

实践截图

在这里插入图片描述

例5

/*join.c*/
#include <pthread.h>
#include <stdio.h>

void *t1_exe(void *arg){
    int i ;
    printf("The first thread: \n") ;
    for(i=1 ;i<6 ;i++)
        printf("%d\n",i) ;
    fflush(stdout);
}

void *t2_exe(void *arg){
    int i ;
    pthread_join((pthread_t)arg, NULL) ;
    printf("The second thread: \n") ;
    for(i=6 ;i<11 ;i++)
        printf("%d\n",i) ;
}

void main() {
    pthread_t pid1,pid2;
    int error1,error2;
    error1=pthread_create(&pid1,NULL, t1_exe,NULL);
    error2=pthread_create(&pid2,NULL, t2_exe,(void *)pid1);
    if (error1!=0||error2!=0) {
        printf("pthread_create failed ");
        return ;
    }
    pthread_join(pid2,NULL);
    return ;
}

实践截图
在这里插入图片描述

例6

tid.c

/*tid.c*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void *t1_exe(void *arg){
    printf("In new created thread\n");
    printf("My pid is %d and my pthread is %d\n",getpid(),(unsigned int)pthread_self());
}

void main() {
    pthread_t pid1;
    int error1;
    error1=pthread_create(&pid1,NULL, t1_exe,NULL);
    if (error1!=0) {
        printf("pthread_create failed ");
        return ;
    }
    printf("In main process\n");
    printf("My pid is %d and my pthread is %d\n",getpid(),(unsigned int)pthread_self());
    pthread_join(pid1,NULL);
    return ;
}

实践截图

在这里插入图片描述

例7

nomutex.c

/*nomutex.c*/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

char str[] = "abcdefghijklmnopqrstuvwxyz123456789";
int index2=0;

void *t1_exe(void *arg){
    while (index2<strlen(str)-1){
        printf("The %dth element of array is %c\n",index2,str[index2]);
        sleep(1);
        index2++;
    }
}

void main() {
    pthread_t pid1,pid2;
    int error1,error2;
    error1=pthread_create(&pid1,NULL, t1_exe,NULL);
    error2=pthread_create(&pid2,NULL, t1_exe,NULL);
    if (error1!=0||error2!=0) {
        printf("pthread_create failed ");
        return ;
    }
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    return ;
}

实践截图
在这里插入图片描述

例8

semmutex_thread.c

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#define MAX 100

static sem_t sem; //互斥量
static int global=1;

void *t1_exe(void *arg) {
    while (global<MAX) {
        sem_wait(&sem);
        global++;
        printf("In thread1 before increment global=%d\n",global);
        sem_post(&sem);
        sleep(5);
    }
}

void *t2_exe(void *arg) {
    while (global<MAX) {
        sem_wait(&sem);
        printf("In thread2 before double global=%d\n",global);
        global*=2;
        printf("In thread2 after double global=%d\n",global);
        sem_post(&sem);
        sleep(6);
    }
}

void main() {
    pthread_t pid1,pid2;
    int error1,error2;
    if ( sem_init (&sem,0 , 1)==-1 ) {
        perror("sem initialized failed");
        exit(0);
    }
    error1=pthread_create(&pid1,NULL, t1_exe,NULL);
    error2=pthread_create(&pid2,NULL, t2_exe,NULL);
    if (error1!=0||error2!=0) {
        printf("pthread_create failed ");
        return ;
    }
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    sem_destroy(&sem);
    return ;
}

实践截图
在这里插入图片描述

例9

cond.c

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 添加这一行以包含 sleep 函数的声明
#define MAX 100
pthread_mutex_t mutex;
pthread_cond_t cond ;

void *thread1(void *);
void *thread2(void *);

int i=1;
int main(void){
    pthread_t t_a;
    pthread_t t_b;
    pthread_mutex_init (&mutex,NULL); //互斥量的初始化
    pthread_cond_init (&cond,NULL); //条件变量的初始化
    pthread_create(&t_a,NULL,thread1,(void *)NULL); //创建进程 t_a/
    pthread_create(&t_b,NULL,thread2,(void *)NULL); //创建进程 t_b/
    pthread_join(t_b, NULL); //等待进程 t_b 结束*
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    exit(0);
}

void *thread1(void *flag)
{
    for(i=1;i<MAX;i++){
        pthread_mutex_lock(&mutex); //锁住互斥量*/
        if (i%9==0)
            pthread_cond_signal(&cond); //条件改变,发送信号,通知 t_b 进程*/
        else
            printf("In thread1:%d\n",i);
        pthread_mutex_unlock(&mutex); //解锁互斥量*/
        sleep(1);
    }
}

void *thread2(void *flag)
{
    while(i<MAX)
    {
        pthread_mutex_lock(&mutex);
        printf("In thread2 before wait\n");
        if (i%9!=0)
            pthread_cond_wait(&cond,&mutex); //等待并释放 mutex*/
        printf("In thread2 after wait\n");
        if (i%9==0)
            printf("In thread2:%d\n",i);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

实践截图
在这里插入图片描述

习题1

xt1.c

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

#define BUFFER_SIZE 100

typedef struct {
    char buffer[BUFFER_SIZE];
    int in;
    int out;
    pthread_mutex_t mutex;
    pthread_cond_t full;
    pthread_cond_t empty;
} Buffer;

void* producer(void* arg) {
    Buffer* buf = (Buffer*)arg;
    int item;
    while (1) {
        item = rand() % 256; // 生产一个随机的 ASCII 字符
        pthread_mutex_lock(&buf->mutex);
        while ((buf->in + 1) % BUFFER_SIZE == buf->out) {
            // 缓冲区满,等待消费者
            pthread_cond_wait(&buf->full, &buf->mutex);
        }
        buf->buffer[buf->in] = item;
        printf("Produced: %c\n", buf->buffer[buf->in]);
        buf->in = (buf->in + 1) % BUFFER_SIZE;
        pthread_cond_signal(&buf->empty);
        pthread_mutex_unlock(&buf->mutex);
        sleep(1); // 生产者休眠一段时间
    }
    return NULL;
}

void* consumer(void* arg) {
    Buffer* buf = (Buffer*)arg;
    int item;
    while (1) {
        pthread_mutex_lock(&buf->mutex);
        while (buf->in == buf->out) {
            // 缓冲区空,等待生产者
            pthread_cond_wait(&buf->empty, &buf->mutex);
        }
        item = buf->buffer[buf->out];
        printf("Consumed: %c\n", buf->buffer[buf->out]);
        buf->out = (buf->out + 1) % BUFFER_SIZE;
        pthread_cond_signal(&buf->full);
        pthread_mutex_unlock(&buf->mutex);
        sleep(1); // 消费者休眠一段时间
    }
    return NULL;
}

int main() {
    pthread_t p1, p2, c1, c2, c3, c4, c5;
    Buffer buf;

    // 初始化缓冲区和同步原语
    buf.in = 0;
    buf.out = 0;
    pthread_mutex_init(&buf.mutex, NULL);
    pthread_cond_init(&buf.full, NULL);
    pthread_cond_init(&buf.empty, NULL);

    // 创建生产者和消费者线程
    pthread_create(&p1, NULL, producer, &buf);
    pthread_create(&p2, NULL, producer, &buf);
    pthread_create(&c1, NULL, consumer, &buf);
    pthread_create(&c2, NULL, consumer, &buf);
    pthread_create(&c3, NULL, consumer, &buf);
    pthread_create(&c4, NULL, consumer, &buf);
    pthread_create(&c5, NULL, consumer, &buf);

    // 等待线程结束
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_join(c1, NULL);
    pthread_join(c2, NULL);
    pthread_join(c3, NULL);
    pthread_join(c4, NULL);
    pthread_join(c5, NULL);

    // 销毁同步原语
    pthread_mutex_destroy(&buf.mutex);
    pthread_cond_destroy(&buf.full);
    pthread_cond_destroy(&buf.empty);

    return 0;
}

实践截图
在这里插入图片描述

习题2

线程同步与互斥机制的主要特点简单总结如下:

  1. 互斥锁(Mutex)

    • 同一时间只允许一个线程访问资源
    • 开销小,适合短期加锁
    • 需注意死锁问题
  2. 信号量(Semaphore)

    • 控制同时访问的线程数量
    • 适合生产者-消费者等场景
    • 比互斥锁更灵活
  3. 条件变量(Condition Variable)

    • 需配合互斥锁使用
    • 线程可等待特定条件成立
    • 适合复杂协作场景
  4. 读写锁(Read-Write Lock)

    • 读操作可共享,写操作独占
    • 适合读多写少的场景
    • 提高并发性能

简单选择建议:

  • 简单互斥用互斥锁
  • 控制并发数量用信号量
  • 线程协作用条件变量
  • 读多写少用读写锁

习题3

xt3

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

#define NUM_PRINTS 10

// 全局变量
pthread_mutex_t mutex;
pthread_cond_t condA, condB, condC;
int turn = 0; // 用于控制打印顺序

// 线程函数
void* threadA(void* arg) {
    for (int i = 0; i < NUM_PRINTS; i++) {
        pthread_mutex_lock(&mutex);
        while (turn != 0) pthread_cond_wait(&condA, &mutex);
        printf("A ");
        turn = 1;
        pthread_cond_signal(&condB);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* threadB(void* arg) {
    for (int i = 0; i < NUM_PRINTS; i++) {
        pthread_mutex_lock(&mutex);
        while (turn != 1) pthread_cond_wait(&condB, &mutex);
        printf("B ");
        turn = 2;
        pthread_cond_signal(&condC);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* threadC(void* arg) {
    for (int i = 0; i < NUM_PRINTS; i++) {
        pthread_mutex_lock(&mutex);
        while (turn != 2) pthread_cond_wait(&condC, &mutex);
        printf("C ");
        turn = 0;
        pthread_cond_signal(&condA);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t threads[3];

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condA, NULL);
    pthread_cond_init(&condB, NULL);
    pthread_cond_init(&condC, NULL);

    // 创建线程
    pthread_create(&threads[0], NULL, threadA, NULL);
    pthread_create(&threads[1], NULL, threadB, NULL);
    pthread_create(&threads[2], NULL, threadC, NULL);

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

    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&condA);
    pthread_cond_destroy(&condB);
    pthread_cond_destroy(&condC);

    return 0;
}

实践截图
在这里插入图片描述

习题4

因为在没有适当的同步机制下,多个线程同时访问和修改同一个全局变量 global,导致输出结果的不确定性。

在之前的代码示例中,两个线程(生产者)同时增加 global 变量的值,而没有使用互斥锁或其他同步机制来确保每次只有一个线程能够修改 global 变量。当两个线程几乎同时执行到 global++ 语句时,它们可能会读取相同的 global 值,然后各自增加 1,导致其中一个增加操作被覆盖,最终结果是 global 变量的值增加了 1 而不是 2,这就是为什么会出现多个 “The global value is 4” 的原因。

为了改进这个问题,确保程序按照期望的顺序输出 “The global value is 1” 到 “The global value is 5”,我们需要在修改 global 变量时使用互斥锁来同步线程。以下是改进后的代码示例:

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

#define MAX 100
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int global = 1;

void* t1_exe(void *arg) {
    for (int i = 1; i <= MAX; i++) {
        pthread_mutex_lock(&mutex);
        printf("In thread1 before increment global=%d\n", global);
        global++;
        printf("In thread1 after increment global=%d\n", global);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* t2_exe(void *arg) {
    for (int i = 1; i <= MAX; i++) {
        pthread_mutex_lock(&mutex);
        printf("In thread2 before double global=%d\n", global);
        global *= 2;
        printf("In thread2 after double global=%d\n", global);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

int main(void) {
    pthread_t t_a, t_b;
    pthread_mutex_init(&mutex, NULL); // 初始化互斥量
    pthread_create(&t_a, NULL, t1_exe, NULL); // 创建进程 t_a
    pthread_create(&t_b, NULL, t2_exe, NULL); // 创建进程 t_b
    pthread_join(t_a, NULL); // 等待进程 t_a 结束
    pthread_join(t_b, NULL); // 等待进程 t_b 结束
    pthread_mutex_destroy(&mutex);
    return 0;
}

在这个改进后的代码中,我们使用 pthread_mutex_lockpthread_mutex_unlock 来保护对 global 变量的访问,确保每次只有一个线程能够修改它。这样可以避免竞态条件,确保程序的输出结果是确定的。

实践截图
在这里插入图片描述

习题5

xt5.c

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

#define MAX 10

pthread_mutex_t mutex;
pthread_cond_t condA, condB, condC;
int turn = 0;

void* thread1(void* arg) {
    int id = *(int*)arg;
    for (int i = 0; i < MAX; i++) {
        pthread_mutex_lock(&mutex);
        while (turn != id) pthread_cond_wait(&condA, &mutex);
        printf("Thread %d: ", id);
        for (int j = 0; j < 5; j++) {
            printf("%c ", 'A' + id);
        }
        printf("\n");
        turn = (id + 1) % 3;
        if (turn == 0) pthread_cond_signal(&condC);
        else pthread_cond_signal(&condB);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* thread2(void* arg) {
    int id = *(int*)arg;
    for (int i = 0; i < MAX; i++) {
        pthread_mutex_lock(&mutex);
        while (turn != id) pthread_cond_wait(&condB, &mutex);
        printf("Thread %d: ", id);
        for (int j = 0; j < 5; j++) {
            printf("%c ", 'A' + id);
        }
        printf("\n");
        turn = (id + 1) % 3;
        if (turn == 0) pthread_cond_signal(&condC);
        else pthread_cond_signal(&condA);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t t_a, t_b, t_c, t_d;
    int ids[4] = {0, 1, 2, 3};

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condA, NULL);
    pthread_cond_init(&condB, NULL);
    pthread_cond_init(&condC, NULL);

    pthread_create(&t_a, NULL, thread1, &ids[0]);
    pthread_create(&t_b, NULL, thread2, &ids[1]);
    pthread_create(&t_c, NULL, thread1, &ids[2]);
    pthread_create(&t_d, NULL, thread2, &ids[3]);

    pthread_join(t_a, NULL);
    pthread_join(t_b, NULL);
    pthread_join(t_c, NULL);
    pthread_join(t_d, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&condA);
    pthread_cond_destroy(&condB);
    pthread_cond_destroy(&condC);

    return 0;
}

实践截图
在这里插入图片描述

执行结果:

当运行这个程序时,输出结果将严格遵循 A、B、C 的顺序:

分析原因:

同步机制:

通过使用互斥锁和条件变量,我们确保了线程按照特定的顺序执行。每个线程在打印自己的字符后,会更新 turn 变量,并通知下一个线程继续执行。

互斥访问:

互斥锁确保了对共享变量 turn 的访问是互斥的,避免了竞态条件。

条件变量:

条件变量用于线程间的同步等待。每个线程在条件不满足时会等待,直到被其他线程唤醒。

通过引入同步机制,我们可以控制线程的执行顺序,确保输出结果的确定性。

六、学习实践过程遇到的问题与解决方式

1. 进程挂起

问题描述

不了解什么是进程挂起

解决方式

  • 进程挂起(Process Suspend):是一种操作系统功能,允许暂时停止一个进程的执行,而不终止它。挂起的进程不会消耗CPU资源,但仍然保留在内存中或被交换到磁盘上。进程挂起通常用于低优先级任务、系统资源不足时的负载均衡或用户请求暂停执行的场景。
  • 挂起的原因:进程挂起可能由多种原因引起,包括用户请求、系统资源管理、后台任务调度等。例如,用户可能希望暂停一个正在运行的程序以节省资源或检查其状态。
  • 恢复执行:挂起的进程可以通过特定的系统调用或用户操作恢复执行。恢复后,进程从上次挂起的地方继续运行,就像它从未被暂停过一样。
  • 应用场景:进程挂起在多任务操作系统中非常有用,它可以帮助管理系统资源,提高系统效率,并提供对进程执行的更精细控制。

2. 线程终止

问题描述

不了解如何正确终止一个线程。

解决方式

  • 线程终止(Thread Termination):是指在一个线程的执行过程中,由于特定条件或用户请求,提前结束该线程的执行。正确终止线程是多线程编程中的一个重要方面,因为不当的终止可能导致资源泄露或数据不一致。

  • 正常终止:线程可以通过返回语句正常结束其执行。当线程函数执行完毕并返回时,线程会自动终止。

    void* thread_function(void* arg) {
        // 执行一些操作
        return NULL; // 线程正常终止
    }
    
  • 取消线程:在 POSIX 线程(pthread)中,可以使用 pthread_cancel 函数请求取消一个线程。被取消的线程会调用其清理处理程序(如果有的话),然后终止。

    #include <pthread.h>
    pthread_t thread;
    pthread_create(&thread, NULL, thread_function, NULL);
    pthread_cancel(thread); // 请求取消线程
    pthread_join(thread, NULL); // 等待线程终止
    
  • 设置取消点:在某些情况下,线程可能需要执行一些清理操作后再终止。可以通过 pthread_setcancelstatepthread_setcanceltype 函数设置线程的取消状态和类型,确保线程在安全点被取消。

    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 允许取消
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); // 延迟取消,直到线程到达取消点
    // 执行一些操作
    pthread_testcancel(); // 检查取消请求并执行清理操作(如果有的话)
    
  • 资源管理:在线程终止前,确保释放所有分配的资源,如内存、文件句柄等,以避免资源泄露。

七、参考资料

  • AI工具(你使用的AI工具及其链接)
    • 问小白
    • Kimi
  • 图书
    • 《Linux编程基础.李养群》电子版
  • 网站
    • CSDN

“20242817李臻 原创作品转载请注明出处 《Linux高级编程实践》”

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

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

相关文章

浙江大学:DeepSeek如何引领智慧医疗的革新之路?|48页PPT下载方法

导 读INTRODUCTION 随着人工智能技术的飞速发展&#xff0c;DeepSeek等大模型正在引领医疗行业进入一个全新的智慧医疗时代。这些先进的技术不仅正在改变医疗服务的提供方式&#xff0c;还在提高医疗质量和效率方面展现出巨大潜力。 想象一下&#xff0c;当你走进医院&#xff…

Android基础彻底解析-APK入口点,xml,组件,脱壳,逆向

第一章:引言与背景 Android逆向工程,作为一种深入分析Android应用程序的技术,主要目的就是通过分析应用的代码、资源和行为来理解其功能、结构和潜在的安全问题。它不仅仅是对应用进行破解或修改,更重要的是帮助开发者、研究人员和安全人员发现并解决安全隐患。 本文主要对…

ubuntu 2204 安装 vcs 2018

安装评估 系统 : Ubuntu 22.04.1 LTS 磁盘 : ubuntu 自身占用了 9.9G , 按照如下步骤 安装后 , 安装后的软件 占用 13.1G 仓库 : 由于安装 libpng12-0 , 添加了一个仓库 安装包 : 安装了多个包(lsb及其依赖包 libpng12-0)安装步骤 参考 ubuntu2018 安装 vcs2018 安装该…

逆向|中国产业政策大数据平台|请求体加密

2025-04-11 逆向地址:aHR0cDovL3poZW5nY2UuMmIuY24v 打开开发者工具出现debugger,直接注入脚本过掉无限debugger let aaa Function.prototype.constructor; Function.prototype.constructor function (params) { if(params ‘debugger’){ console.log(params); return null…

游戏引擎学习第226天

引言&#xff0c;计划 我们目前的目标是开始构建“元游戏”结构。所谓元游戏&#xff0c;指的是不直接属于核心玩法本身&#xff0c;但又是游戏体验不可或缺的一部分&#xff0c;比如主菜单、标题画面、存档选择、选项设置、过场动画等。我们正在慢慢将这些系统结构搭建起来。…

Notepad++安装Markdown实时预览插件

具体操作 打开notepad -> 插件 -> 插件管理 -> 可用 -> “Markdown Panel” -> 安装&#xff0c;安装完成后工具栏点击"Markdown Panel"按钮。 注意&#xff1a;由于网络等原因可能安装失败 导致工具栏没出现""Markdown Panel"按钮&am…

Mysql-视图和存储过程

视图 1.介绍 视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。 通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条…

stm32面试

数据结构相关问题 stm32面试 数据结构相关问题 目录基础数据结构树与图排序与查找算法 Linux相关问题Linux系统基础Linux命令与脚本Linux网络与服务 操作系统相关问题操作系统基础概念操作系统调度算法操作系统同步与通信 STM32相关问题STM32硬件基础STM32编程与开发STM32应用与…

202524 | 分布式事务

分布式事务&#xff08;Distributed Transaction&#xff09; 分布式事务是指跨多个数据库、服务或系统节点的事务操作&#xff0c;要求所有参与方要么全部成功提交&#xff0c;要么全部回滚&#xff0c;保证数据一致性。 1. 为什么需要分布式事务&#xff1f; 在单体应用中&…

在 macOS 上修改 最大文件描述符限制(Too many open files) 和 网络端口相关参数 需要调整系统级配置的详细步骤

在 macOS 上修改 最大文件描述符限制&#xff08;Too many open files&#xff09; 和 网络端口相关参数 需要调整系统级配置。以下是详细步骤&#xff1a; 在 macOS 上修改 最大文件描述符限制&#xff08;Too many open files&#xff09; 和 网络端口相关参数 需要调整系统级…

通过Arduino IDE向闪存文件系统上传文件

注意&#xff1a;适用于Arduino IDE 2.0版本以上。对于Arduino IDE版本在2.0以下的请参考太极创客的教程&#xff1a;http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-c/spiffs/upload-files/。 1. 下载脚本文件 下载地址&#xff1a;https://github.com/earl…

leetcode 121. Best Time to Buy and Sell Stock

题目描述 本题属于动态规划类问题。 dp数组的含义 dp[i][0]表示从第0天到第i天为止&#xff0c;处于持有股票的状态下&#xff0c;账户里的最大金额。 dp[i][1]表示从第0天到第i天为止&#xff0c;处于不持有股票的状态下&#xff0c;账户里的最大金额。 按照这个定义dp[n-…

【Docker-13】Docker Container容器

Docker Container&#xff08;容器&#xff09; 一、什么是容器&#xff1f; 通俗地讲&#xff0c;容器是镜像的运行实体。镜像是静态的只读文件&#xff0c;而容器带有运行时需要的可写文件层&#xff0c;并且容器中的进程属于运行状态。即容器运行着真正的应用进程。容器有…

蓝宝石狼组织升级攻击工具包,利用新型紫水晶窃密软件瞄准能源企业

网络安全专家发现&#xff0c;被称为"蓝宝石狼"&#xff08;Sapphire Werewolf&#xff09;的威胁组织正在使用升级版"紫水晶"&#xff08;Amethyst&#xff09;窃密软件&#xff0c;对能源行业企业发起复杂攻击活动。此次攻击标志着该组织能力显著提升&am…

关于我的服务器

最近我买了台腾讯云服务器&#xff0c;然后新手小白只会用宝塔。。。 安装完之后默认的端口是8888&#xff0c;打开面板就会提示我有风险。然后 我改了端口之后&#xff0c;怎么都打不开。 于是 学到了几句命令可以使用&#xff1a; //查看端口是否已经修改成功 cat www/se…

vue + element-plus自定义表单验证(修改密码业务)

写一个vue组件Password.vue 没有表单验证只有3个表单项 <template><div><el-form><el-form-item label"旧密码"><el-input></el-input></el-form-item><el-form-item label"新密码"><el-input>&l…

2025年第十八届“认证杯”数学中国数学建模网络挑战赛【BC题】完整版+代码+结果

# 问题一&#xff1a;随机森林回归from sklearn.ensemble import RandomForestRegressormodel_rf RandomForestRegressor()model_rf.fit(X_train, y_train)# 问题二&#xff1a;LSTM时间序列预测from tensorflow.keras.models import Sequentialmodel_lstm Sequential()model…

一、小白如何用Pygame制作一款跑酷类游戏(成品展示+添加背景图和道路移动效果)

小白如何用Pygame制作一款跑酷类游戏 文章目录 小白如何用Pygame制作一款跑酷类游戏前言一、游戏最终效果展示二、创建项目并加载pygame模块1.创建项目2.下载pygame模块3. 项目结构安排 三、添加背景图和实现道路移动效果1.引入库2.窗口设置和资源加载3.游戏主循环和程序入口4.…

基础知识:Dify 安装

官方指南:https://docs.dify.ai/zh-hans/getting-started/install-self-hosted docker & docker-compose 安装 可参考:

JWT令牌:实现安全会话跟踪与登录认证的利器

摘要&#xff1a;本文深入探讨了JWT令牌在实现会话跟踪和登录认证方面的应用&#xff0c;详细介绍了JWT令牌的概念、组成、生成与校验方法&#xff0c;以及在实际案例中如何通过JWT令牌进行会话跟踪和登录认证的具体实现步骤&#xff0c;为系统的安全认证机制提供了全面且深入的…