20242817李臻《Linux⾼级编程实践》第7周
一、AI对学习内容的总结
第八章:多线程编程
8.1 多线程概念
- 进程与线程的区别:
- 进程是资源分配单位,拥有独立的地址空间、全局变量、打开的文件等。
- 线程是调度单位,在同一进程内的线程共享这些资源,通信方便,切换代价小。
- 线程特点:
- 调度资源少,响应快,占用资源少。
- 同一进程内线程共享资源,通信便捷,无需复杂通信机制。
- 提高程序响应时间,适合图形界面等需要及时响应用户操作的场景。
- 提高CPU利用率,尤其在多CPU环境下,线程可并行执行。
- Linux线程:
- 遵循POSIX线程接口(pthread),用户级线程,在用户空间调度。
- 线程的创建、同步、销毁等操作通过pthread库函数实现。
8.2 线程状态与线程编程
8.2.1 线程的创建和参数传递
- pthread_create:创建新线程。
- 参数包括线程ID指针、属性、执行代码函数指针、传递参数。
- 示例代码展示了如何创建线程并传递参数。
- 线程参数传递:通过
pthread_create
的arg
参数传递给线程函数。
8.2.2 线程终止
- pthread_exit:线程正常终止,可返回值给
pthread_join
。 - exit:导致整个进程结束,需谨慎使用。
- 资源清理:
pthread_cleanup_push
和pthread_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_wait
或pthread_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:
在实际开发中,使用线程挂起功能时,需要特别注意以下潜在问题:
- 死锁风险:如果线程在持有资源(如锁)时被挂起,可能会导致其他线程因无法获取资源而陷入死锁。
- 资源泄漏:线程挂起后可能不会释放某些资源(如动态分配的内存),需要确保在恢复执行时能够正确管理这些资源。
- 线程状态同步:挂起和恢复线程时,需要确保线程状态的同步,避免出现不可预期的行为。
- 信号处理:如果使用信号(如SIGSTOP)挂起线程,需要确保信号处理机制不会与其他信号处理逻辑冲突。
- 线程恢复时机:线程恢复执行的时机需要谨慎设计,避免在不合适的时刻恢复线程,导致系统不稳定或数据不一致。
- 我:
针对我该知识点,我理解了吗?我的回答结束了,请对我的回答进行评价总结。
- 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
线程同步与互斥机制的主要特点简单总结如下:
-
互斥锁(Mutex)
- 同一时间只允许一个线程访问资源
- 开销小,适合短期加锁
- 需注意死锁问题
-
信号量(Semaphore)
- 控制同时访问的线程数量
- 适合生产者-消费者等场景
- 比互斥锁更灵活
-
条件变量(Condition Variable)
- 需配合互斥锁使用
- 线程可等待特定条件成立
- 适合复杂协作场景
-
读写锁(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_lock
和 pthread_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_setcancelstate
和pthread_setcanceltype
函数设置线程的取消状态和类型,确保线程在安全点被取消。pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 允许取消 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); // 延迟取消,直到线程到达取消点 // 执行一些操作 pthread_testcancel(); // 检查取消请求并执行清理操作(如果有的话)
-
资源管理:在线程终止前,确保释放所有分配的资源,如内存、文件句柄等,以避免资源泄露。
七、参考资料
- AI工具(你使用的AI工具及其链接)
- 问小白
- Kimi
- 图书
- 《Linux编程基础.李养群》电子版
- 网站
- CSDN
“20242817李臻 原创作品转载请注明出处 《Linux高级编程实践》”