当前位置: 首页 > article >正文

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

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)。

三、学习思维导图

第八章

在这里插入图片描述

mindmaproot((多线程编程))thread_concept((线程概念))thread_vs_process((线程与进程的关系))进程是资源分配单位,线程是调度单位线程共享进程的内存空间(代码、全局变量、堆栈)同一进程内的线程通信方便thread_features((线程特点))轻量级进程(创建和切换代价小)调度资源少,响应快提高程序响应(如 GUI 应用)提高 CPU 利用率(多核支持)thread_life_cycle((线程生命周期))thread_creation((线程的创建))pthread_create参数:线程 ID 指针、属性、线程函数、参数示例:创建线程并传递结构体参数线程参数传递通过 void* 类型传递任意数据示例:传递学生信息结构体thread_termination((线程的终止))pthread_exit用于正常终止线程返回值传递给 pthread_joinreturn 语句线程函数执行完毕后自然终止exit 语句结束整个进程(所有线程)示例:exit 和 pthread_exit 的区别thread_management((线程管理))pthread_join等待线程结束示例:线程 2 等待线程 1 打印 1-5 后打印 6-10pthread_self获取当前线程 ID示例:打印线程 ID 和进程 ID线程资源清理pthread_cleanup_push/pop示例:线程结束时执行清理函数thread_example((线程共享变量))示例:两个线程共享全局变量线程 1 打印变量值线程 2 修改变量值synchronization((线程同步与互斥))mutex((互斥量))互斥量的初始化与销毁静态初始化:PTHREAD_MUTEX_INITIALIZER动态初始化:pthread_mutex_init互斥量的加锁与解锁pthread_mutex_lockpthread_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_waitpthread_cond_signal示例:线程等待特定条件线程 1 增加全局变量线程 2 等待并打印 9 的倍数examples((实例应用))shared_variable_example((共享变量访问))无互斥量导致冲突使用互斥量解决冲突producer_consumer_example((生产者-消费者问题))缓冲区大小为 1002 个生产者随机生成字符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 函数的返回类型为 intpthread_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 100static 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 进程*/elseprintf("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 100typedef 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_apthread_create(&t_b, NULL, t2_exe, NULL); // 创建进程 t_bpthread_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 10pthread_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高级编程实践》”

相关文章:

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

20242817李臻《Linux⾼级编程实践》第7周 一、AI对学习内容的总结 第八章&#xff1a;多线程编程 8.1 多线程概念 进程与线程的区别&#xff1a; 进程是资源分配单位&#xff0c;拥有独立的地址空间、全局变量、打开的文件等。线程是调度单位&#xff0c;在同一进程内的线程…...

浙江大学: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 安装该…...

Express中间件(Middleware)详解:从零开始掌握(3)

实用中间件模式25例 1. 基础增强模式 请求属性扩展 function extendRequest() {return (req, res, next) > {req.getClientLanguage () > {return req.headers[accept-language]?.split(,)[0] || en;};next();}; } 响应时间头 function responseTime() {return (r…...

深入理解微信小程序开发:架构、组件化与进阶实战

&#x1f4d8;博文正文&#xff1a; 深入理解微信小程序开发&#xff1a;架构、组件化与进阶实战 微信小程序已成为移动互联网的重要入口。随着业务复杂度提升&#xff0c;仅靠入门知识已无法应对日常开发需求。本文将深入剖析小程序开发架构、组件化模式、状态管理、网络封装…...

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

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

在SpringBoot中访问 static 与 templates 目录下的内容

目录 步骤一&#xff1a;添加 Thymeleaf 依赖 (处理 Templates 目录)步骤二&#xff1a;配置静态资源路径 (可选但建议了解)步骤三&#xff1a;访问不同目录下的 HTML 文件访问 static 目录下的 HTML 文件访问 templates 目录下的 HTML 文件 总结 在使用 Spring Boot 开发 Web …...

游戏引擎学习第226天

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

青少年编程与数学 02-016 Python数据结构与算法 22课题、并行算法

青少年编程与数学 02-016 Python数据结构与算法 22课题、并行算法 一、GPU并行计算矩阵乘法示例 二、MPI并行计算allgather操作示例 三、Python中的并行计算多线程并行计算多进程并行计算 四、SIMD并行计算SIMD并行计算示例 总结 课题摘要: 并行算法是通过同时执行多个任务或操…...

Ubuntu系统18.04更新驱动解决方法

原始是&#xff1a;ubuntu18.04里面的驱动是470&#xff0c;对应cuda11.4 现在需要更新为525&#xff0c;对应cuda为12.0 实现&#xff1a; 1、打开终端 Ctrl Alt T2、使用 lspci 命令&#xff08;快速查看显卡型号&#xff09; lspci | grep -i vga3、终端输入 ubuntu-d…...

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

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

Mysql-视图和存储过程

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

FreeRTOS入门与工程实践-基于STM32F103(二)(互斥量,事件组,任务通知,软件定时器,中断管理,资源管理,调试与优化)

互斥量 一、互斥量&#xff08;Mutex&#xff09;&#xff1a;解决多任务 “抢资源” 的问题 1. 是什么&#xff1f; 互斥量是一种 “任务间互斥访问资源” 的工具&#xff0c;本质是一个 只能被锁定&#xff08;0&#xff09;或释放&#xff08;1&#xff09;的二进制信号量…...

stm32面试

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

202524 | 分布式事务

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

Python 企业级架构实战(上篇)

深入企业级系统设计与高可用架构,掌握构建可扩展 Python 系统的核心技能。 41. 微服务架构设计与 FastAPI 实现 多服务协同开发示例 # 用户服务 (user_service/main.py) from fastapi import FastAPI app = FastAPI() users_db = { 1: {"id": 1, "name&…...

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

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

Python 文本和字节序列(字符问题)

本章将讨论下述话题&#xff1a; 字符、码位和字节表述 bytes、bytearray 和 memoryview 等二进制序列的独特特性 全部 Unicode 和陈旧字符集的编解码器 避免和处理编码错误 处理文本文件的最佳实践 默认编码的陷阱和标准 I/O 的问题 规范化 Unicode 文本&#xff0c;进行安全的…...

通过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;并且容器中的进程属于运行状态。即容器运行着真正的应用进程。容器有…...

LoadableTransportInfo函数分析之RPCRT4!LOADABLE_TRANSPORT::LOADABLE_TRANSPORT初始化过程

LoadableTransportInfo函数分析 第一部分&#xff1a; RPC_STATUS LoadableTransportInfo ( IN RPC_CHAR * DllName, IN RPC_CHAR PAPI * RpcProtocolSequence, OUT TRANS_INFO * PAPI *pTransInfo ) { 。。。。。。。 pTransportInterface (*TransportLo…...

大模型预标注和自动化标注在OCR标注场景的应用

OCR&#xff0c;即光学字符识别&#xff0c;简单来说就是利用光学设备去捕获图像并识别文字&#xff0c;最终将图片中的文字转换为可编辑和可搜索的文本。在数字化时代&#xff0c;OCR&#xff08;光学字符识别&#xff09;技术作为处理图像中文字信息的关键手段&#xff0c;其…...

Zookeeper 命令返回数据的含义

下面详细讲解这三个 Zookeeper 命令返回数据的含义&#xff1a; 1. ls /path - 列出子节点 命令功能&#xff1a; 列出指定路径下的所有直接子节点名称&#xff08;不包含孙子节点&#xff09; 示例返回&#xff1a; [child1, child2, child3] 输出解析&#xff1a; 返回…...

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

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

2025蓝桥杯python A组省赛 题解

真捐款去了&#xff0c;好长时间没练了&#xff0c;感觉脑子和手都不转悠了。 B F BF BF 赛时都写假了&#xff0c; G G G 也只写了爆搜。 题解其实队友都写好了&#xff0c;我就粘一下自己的代码&#xff0c;稍微提点个人的理解水一篇题解 队友题解 2025蓝桥杯C A组省赛 题…...

JMeter重要的是什么

重要特性 支持多种协议&#xff1a; JMeter支持对多种协议进行性能测试&#xff0c;包括HTTP、HTTPS、FTP、JDBC&#xff08;数据库&#xff09;、LDAP、JMS、SOAP、REST等。这使得它能够适应各种不同的测试场景。强大的负载模拟能力&#xff1a; JMeter能够模拟大量的虚拟用户…...

深入探索如何压缩 WebAssembly

一、初始体积&#xff1a;默认 Release 构建 我们从最基础的构建开始&#xff0c;不开启调试符号&#xff0c;仅使用默认的 release 模式&#xff1a; $ wc -c pkg/wasm_game_of_life_bg.wasm 29410 pkg/wasm_game_of_life_bg.wasm这是我们优化的起点 —— 29,410 字节。 二…...

浅谈SQL Server系统内核管理机制

浅谈SQL Server系统内核管理机制 应用环境 Microsoft Windows 10.0.19045.5487 x64 专业工作站版 22H2Microsoft SQL Server 2019 - 15.0.2130.3 (X64)SQL Server Management Studio -18.6 laster 文章目录 浅谈SQL Server系统内核管理机制数据库和文件服务器管理视图系统目录…...