目录
- 前言
- 0010第一个多线程程序
- 0020多线程与显示线程ID
- 0030传参给线程测试
- 0041终止线程1_pthread_exit
- 0042终止线程2_pthread_cancel
- 0050接收返回值_pthread_join
- 0060线程响应cancel信号及响应的方式
- 0070线程执行多个任务函数
- 0080LTS线程局部存储技术
- 0090线程不同步导致的数据错乱问题
- 0101使用宏定义初始化互斥锁
- 0102使用函数初始化互斥锁
- 0111信号量_二进制信号量
- 0112信号量_计数信号量
- 0120条件变量与互斥锁
- 0130读写锁
- 0141死锁案例1_单互斥锁
- 0142死锁案例2_双互斥锁
- 0151线程分离1_pthread_detach函数
- 0152线程分离2_pthread_attr_init函数
- 额外内容
- 9011nanosleep延时函数1
- 9012nanosleep延时函数2
前言
1、编程环境
编码所用IDE:VScode 1.87.0
编译工具:gcc version 11.4.0
运行环境:
1、Windows Subsystem for Linux (WSL) 2
2、Ubuntu 22.04.4 LTS
2、编译命令
如无特别说明,通用编译命令为:
gcc -o main -std=c99 -Wall -pthread filename.c
3、标题名前缀解释
XXX1或XXX2中,XXX代表一个主题,凡是具有相同的XXX,都是同一主题。
1代表服务端内容(代码),2代表客户端内容(代码)。
0010第一个多线程程序
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <pthread.h> // 引入POSIX线程库
#include <unistd.h> // 引入unistd库,提供sleep函数的原型
/**
* @brief 子线程的入口函数
* 这个函数会在子线程启动时被调用执行。
* @param arg 线程启动参数,这里未使用
* @return void* 返回类型为void*,这里返回NULL
*/
void *threadFun(void *arg)
{
printf("这是子线程。\n"); // 打印子线程信息
return NULL; // 子线程结束,返回NULL
}
int main(int argc, char const *argv[])
{
pthread_t threadID; // 表示线程ID的变量
// 创建一个新线程,线程ID存储在threadID中,新线程执行threadFun函数,不传递参数
pthread_create(&threadID, NULL, threadFun, NULL);
printf("这是主线程\n"); // 打印主线程信息
sleep(1); // 主线程休眠1秒,确保子线程有机会执行
return 0; // 主线程正常退出,返回0
}
运行结果:
0020多线程与显示线程ID
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <unistd.h> // 引入unistd库,提供sleep函数的原型
#include <pthread.h> // 引入POSIX线程库
/**
* @brief 子线程的入口函数
* 这个函数会在每个子线程启动时被调用执行。
* @param arg 线程启动参数,这里未使用
* @return void* 返回类型为void*,这里返回NULL
*/
void *threadFun(void *arg)
{
printf("这是子线程,ID为:%ld\n", pthread_self()); // 打印子线程的ID
return NULL; // 子线程结束,返回NULL
}
int main(int argc, char const *argv[])
{
pthread_t threadID[5]; // 用于存储5个线程ID的数组
// 创建5个新线程,每个线程执行threadFun函数,不传递参数
for (int i = 0; i < 5; i++)
{
pthread_create(&threadID[i], NULL, threadFun, NULL);
}
printf("这是主线程。\n"); // 打印主线程信息
sleep(1); // 主线程休眠1秒,确保子线程有机会执行
return 0; // 主线程正常退出,返回0
}
运行结果:
0030传参给线程测试
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <unistd.h> // 引入unistd库,提供sleep函数的原型
#include <pthread.h> // 引入POSIX线程库
/**
* @brief 子线程的入口函数
* 这个函数会在每个子线程启动时被调用执行。
* @param arg 线程启动参数,可以是任何类型的指针
* @return void* 返回类型为void*,这里返回NULL
*/
void *threadFun(void *arg)
{
if (arg == NULL)
{
printf("线程 %ld 未接收到参数。\n", pthread_self()); // 如果参数为NULL,打印未接收到参数的信息
}
else
{
printf("线程 %ld 接收到的参数为:%s\n", pthread_self(), (char *)arg); // 如果参数不为NULL,打印接收到的参数
}
return NULL; // 子线程结束,返回NULL
}
int main(int argc, const char *argv[])
{
pthread_t threadID1, threadID2; // 用于存储两个线程ID的变量
int res; // 用于存储线程创建函数的返回值
// 创建第一个线程,不传递参数
res = pthread_create(&threadID1, NULL, threadFun, NULL);
if (res != 0)
{
perror("创建线程1失败!"); // 如果线程创建失败,打印错误信息
return -1; // 创建失败,返回-1
}
// 创建第二个线程,传递命令行第一个参数作为线程参数
const char *string = argv[0]; // 获取命令行第一个参数
res = pthread_create(&threadID2, NULL, threadFun, (void *)string);
if (res != 0)
{
perror("创建线程2失败!"); // 如果线程创建失败,打印错误信息
return -1; // 创建失败,返回-1
}
sleep(1); // 主线程休眠1秒,确保子线程有机会执行
return 0; // 主线程正常退出,返回0
}
运行结果:
0041终止线程1_pthread_exit
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <unistd.h> // 引入Unix标准函数库,提供对POSIX操作系统API的访问
#include <pthread.h> // 引入线程库,提供线程操作的相关函数
/**
* threadFun - 线程执行函数
* @arg: 线程函数的参数,这里未使用
*
* 描述:该函数使用pthread_exit退出,发送一个返回值。
* 注意:pthread_exit会结束当前线程,并设置线程的退出状态。
* 该函数不能返回局部变量的指针,因为当函数退出时,
* 局部变量所占用的内存将被释放。
*/
void *threadFun(void *arg)
{
/**
* pthread_exit() 函数不能返回一个指向局部数据的指针
* pthread_exit() 函数只会终止当前线程,不会影响其它线程的执行
* pthread_exit() 函数会自动调用线程清理程序, return 不具备这个能力
**/
pthread_exit("subString_success"); // 设置线程的退出状态为"subString_success"
// 以下代码不会被执行,因为pthread_exit会结束当前线程
printf("子线程不会执行到此处");
}
int main(int argc, char const *argv[])
{
pthread_t threadID; // 声明一个线程ID变量
// 尝试创建一个新线程
if (pthread_create(&threadID, NULL, threadFun, NULL))
{
perror("子线程创建失败!"); // 如果创建失败,打印错误信息
return -1; // 创建失败,返回-1
}
void *subThreadRet = NULL; // 声明一个指针用于接收线程的返回值
int res = pthread_join(threadID, &subThreadRet); // 等待线程结束,并获取返回值
if (res != 0)
{
perror("子线程等待失败!"); // 如果等待失败,打印错误信息
return -1; // 等待失败,返回-1
}
printf("接收到的返回值为:%s\n", (char *)subThreadRet); // 打印线程返回的信息
return 0; // 程序执行成功,返回0
}
运行结果:
0042终止线程2_pthread_cancel
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <pthread.h> // 引入POSIX线程库
#include <unistd.h> // 引入unistd库,提供sleep函数的原型
/**
* @brief 子线程的入口函数
* 这个函数会在子线程启动时被调用执行。
* @param arg 线程启动参数,这里未使用
* @return void* 返回类型为void*,这里返回NULL
*/
void *threadFun(void *arg)
{
printf("线程 %ld 已创建,且准备休眠\n", pthread_self()); // 打印子线程ID和准备休眠的信息
sleep(10); // 子线程休眠10秒
return NULL; // 子线程结束,返回NULL
}
int main(int argc, char const *argv[])
{
pthread_t threadID; // 用于存储线程ID的变量
int res; // 用于存储线程操作函数的返回值
// 创建一个新线程,线程ID存储在threadID中,新线程执行threadFun函数,不传递参数
res = pthread_create(&threadID, NULL, threadFun, NULL);
if (res != 0)
{
perror("线程创建失败!"); // 如果线程创建失败,打印错误信息
return -1; // 创建失败,返回-1
}
sleep(1); // 主线程休眠1秒,确保子线程有机会执行
// 向目标线程发送 Cancel 信号,目标线程是否响应,由目标线程决定
res = pthread_cancel(threadID);
if (res != 0)
{
perror("线程取消失败!"); // 如果取消线程失败,打印错误信息
return -1; // 取消失败,返回-1
}
void *subThreadRet = NULL; // 用于存储子线程返回值的指针
// 等待子线程结束,并获取子线程的返回值
res = pthread_join(threadID, &subThreadRet);
if (res != 0)
{
perror("等待线程失败!"); // 如果等待线程失败,打印错误信息
return -1; // 等待失败,返回-1
}
// 检查子线程是否被取消
if (subThreadRet == PTHREAD_CANCELED)
{
printf("线程被强制停止!\n"); // 子线程被取消,打印相关信息
}
else
{
printf("无法强制停止线程。\n"); // 子线程未被取消,打印相关信息
}
return 0; // 主线程正常退出,返回0
}
运行结果:
0050接收返回值_pthread_join
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <stdlib.h> // 引入标准库,提供malloc和free函数
#include <pthread.h> // 引入线程库
#include <errno.h> // 引入错误号库
// 定义线程结果的结构体
typedef struct
{
int value; // 整数值
char message[50]; // 信息字符串
} ThreadResult;
/**
* @brief 线程函数,用于处理传入的整型参数,并返回一个包含处理结果的ThreadResult结构体指针。
*
* @param arg 指向整型参数的指针。
* @return void* 返回一个指向ThreadResult结构体的指针,包含处理结果。
*/
void *threadFun(void *arg)
{
int num = *(int *)arg; // 获取传入的参数
printf("子线程所接收到的参数为:%d\n", num); // 打印参数值
ThreadResult *result = (ThreadResult *)malloc(sizeof(ThreadResult)); // 分配内存
result->value = num * 2; // 计算结果
sprintf(result->message, "子线程的处理结果为:%d", result->value); // 格式化信息字符串
pthread_exit(result); // 退出线程,并返回结果
}
int main(int argc, char const *argv[])
{
pthread_t threadID; // 线程ID
int threadArg = 10; // 传递给线程的参数
// 创建线程
if (pthread_create(&threadID, NULL, threadFun, (void *)&threadArg) != 0)
{
perror("创建线程失败!"); // 如果创建失败,打印错误信息
return -1; // 返回错误码
}
void *subThreadRet = NULL; // 子线程返回的结果
// 等待线程结束,并获取返回值
if (pthread_join(threadID, &subThreadRet) != 0)
{
perror("等待线程失败!"); // 如果等待失败,打印错误信息
return -1; // 返回错误码
}
ThreadResult *result = (ThreadResult *)subThreadRet; // 获取结果
printf("主线程接收到子线程返回的数据为:\n整型数据:%d\t信息:%s\n", result->value, result->message); // 打印结果
free(result); // 释放内存
// 再次尝试等待同一个线程,预期会失败
int res = pthread_join(threadID, &subThreadRet);
if (res == ESRCH)
{
perror("等待线程失败!"); // 如果失败,打印错误信息
}
return 0; // 程序结束
}
运行结果:
0060线程响应cancel信号及响应的方式
相关代码:
#include <unistd.h> // 包含unistd.h头文件,提供对POSIX操作系统API的访问
#include <stdio.h> // 包含stdio.h头文件,提供标准输入输出库函数
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <stdlib.h> // 包含stdlib.h头文件,提供常用工具函数如exit()
/**
* threadFun - 线程的执行函数
* @arg: 线程参数,未使用
*
* 返回:NULL
*
* 功能:打印线程开始执行的信息,设置线程可以被取消,并进入一个无限循环。
*/
void *threadFun(void *arg)
{
printf("线程 %ld 开始执行。\n", pthread_self()); // 打印线程ID和开始执行的信息
// 用于设置线程是否响应取消请求
if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
{
perror("设置可取消线程运行状态失败!"); // 如果设置失败,打印错误信息
return NULL; // 返回NULL,表示线程执行失败
}
// 用于设置线程收到取消请求后的行为
if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0)
{
perror("设置线程接收到cancel信号则直接结束运行状态失败!"); // 如果设置失败,打印错误信息
return NULL; // 返回NULL,表示线程执行失败
}
while (1) // 无限循环,等待线程被取消
;
return NULL; // 正常情况下不会执行到这里
}
/**
* main - 主函数
* @argc: 命令行参数数量
* @argv: 命令行参数数组
*
* 返回:返回码,0表示成功,-1表示失败
*
* 功能:创建一个线程,等待一秒后尝试取消该线程,并等待线程结束,最后输出线程的结束状态。
*/
int main(int argc, char const *argv[])
{
pthread_t threadID; // 定义线程ID变量
if (pthread_create(&threadID, NULL, threadFun, NULL) != 0)
{
perror("创建子线程失败!"); // 如果创建失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
sleep(1); // 休眠1秒,等待线程开始执行
if (pthread_cancel(threadID) != 0)
{
perror("取消线程失败!"); // 如果取消失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
void *subThreadRet = NULL; // 定义子线程返回值变量
if (pthread_join(threadID, &subThreadRet) != 0)
{
perror("等待线程运行结束失败!"); // 如果等待失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
if (subThreadRet == PTHREAD_CANCELED)
{
printf("线程被强制终止!\n"); // 如果线程被取消,打印线程被强制终止的信息
}
else
{
printf("强制终止线程失败!\n"); // 如果线程未被取消,打印强制终止线程失败的信息
}
return 0; // 返回0,表示程序执行成功
}
运行结果:
0070线程执行多个任务函数
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <unistd.h> // 引入Unix标准函数库,主要处理Unix标准系统调用
#include <stdlib.h> // 引入标准库,主要包含了一些常用工具函数
#include <pthread.h> // 引入多线程处理库
/**
* threadFun2 - 输出当前线程ID的函数
*
* 返回:无
*/
void threadFun2(void)
{
printf("threadFun2 => 本线程的ID为:%ld\n", pthread_self()); // 输出当前线程的ID
}
/**
* threadFun1 - 输出当前线程ID并调用threadFun2的函数
* @arg: 线程参数,这里未使用
*
* 返回:无实际意义,仅为了满足函数指针类型要求
*/
void *threadFun1(void *arg)
{
printf("threadFun1 => 本线程的ID为:%ld\n", pthread_self()); // 输出当前线程的ID
threadFun2(); // 调用threadFun2函数
return NULL; // 线程函数返回NULL
}
/**
* main - 主函数,程序的入口
* @argc: 命令行参数个数
* @argv: 命令行参数数组
*
* 返回:整数,程序退出状态码
*/
int main(int argc, char const *argv[])
{
pthread_t threadID; // 声明一个线程ID变量
pthread_create(&threadID, NULL, threadFun1, NULL); // 创建一个新线程,执行threadFun1函数
pthread_join(threadID, NULL); // 等待指定的线程结束
return 0; // 程序正常退出
}
运行结果:
0080LTS线程局部存储技术
相关代码:
#include <stdio.h> // 包含标准输入输出库函数
#include <stdlib.h> // 包含常用工具函数如malloc()和free()
#include <unistd.h> // 包含unistd.h头文件,提供对POSIX操作系统API的访问
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
pthread_key_t key; // 声明一个线程特定数据(TSD)键
/**
* cleanup - 线程清理函数
* @value: 需要清理的数据
*
* 功能:打印线程ID和需要清理的key值,并释放内存。
*/
void cleanup(void *value)
{
printf("线程ID:%ld,需要清理的key值:%d\n", pthread_self(), *(int *)value);
free(value); // 释放分配的内存
}
/**
* print_specific - 打印线程特定数据
*
* 功能:获取并打印当前线程的特定数据。
*/
void print_specific(void)
{
int *thread_data = (int *)pthread_getspecific(key); // 获取当前线程的特定数据
printf("线程ID:%ld,线程的key值:%d\n", pthread_self(), *thread_data);
}
/**
* threadFun - 线程执行函数
* @arg: 线程参数,指向需要设置的特定数据的指针
*
* 返回:NULL
*
* 功能:为当前线程分配特定数据,并设置到TSD中,然后打印出来。
*/
void *threadFun(void *arg)
{
int *thread_data = (int *)malloc(sizeof(int)); // 分配内存用于存储特定数据
*thread_data = *(int *)arg; // 将线程参数复制到分配的内存中
if (pthread_setspecific(key, thread_data) != 0) // 将特定数据设置到TSD中
{
perror("存储线程的值失败!"); // 如果设置失败,打印错误信息
return NULL; // 返回NULL,表示线程执行失败
}
print_specific(); // 打印当前线程的特定数据
return NULL; // 线程执行完毕,返回NULL
}
/**
* main - 主函数
*
* 返回:返回码,0表示成功,-1表示失败
*
* 功能:创建一个TSD键,创建并等待多个线程,每个线程设置并打印自己的特定数据,最后销毁TSD键。
*/
int main(int argc, char const *argv[])
{
if (pthread_key_create(&key, cleanup) != 0) // 创建TSD键并设置清理函数
{
perror("初始化key失败!"); // 如果创建失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
pthread_t threadID[5]; // 声明线程ID数组
int thread_arg[5] = {0}; // 初始化线程参数数组
for (int i = 0; i < 5; i++)
{
thread_arg[i] = i; // 设置每个线程的参数
if (pthread_create(&threadID[i], NULL, threadFun, (void *)&thread_arg[i]) != 0)
{
perror("线程创建失败!"); // 如果创建失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
}
for (int i = 0; i < 5; i++)
{
if (pthread_join(threadID[i], NULL) != 0) // 等待每个线程结束
{
perror("等待线程失败!"); // 如果等待失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
}
if (pthread_key_delete(key) != 0) // 销毁TSD键
{
perror("主线程销毁key失败!"); // 如果销毁失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
return 0; // 返回0,表示程序执行成功
}
运行结果:
0090线程不同步导致的数据错乱问题
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <stdlib.h> // 引入标准库,主要包含了一些常用工具函数
#include <pthread.h> // 引入多线程处理库
#include <unistd.h> // 引入Unix标准函数库,主要处理Unix标准系统调用
// 全局变量,模拟总的票数
int ticket_sum = 10;
/**
* sell_ticket - 模拟售票员卖票的函数
* @arg: 线程参数,这里未使用
*
* 返回:无实际意义,仅为了满足函数指针类型要求
*/
void *sell_ticket(void *arg)
{
int i;
// 4个售票员负责将10张票全部卖出
for (i = 0; i < 10; i++)
{
// 直至所有票全部卖出,4个售票员才算完成任务
if (ticket_sum > 0)
{
sleep(1); // 模拟售票过程中时间的消耗
// 每个线程代表一个售票员
printf("%ld 卖第 %d 张票\n", pthread_self(), 10 - ticket_sum + 1);
ticket_sum--; // 票数减少
}
}
return 0;
}
/**
* main - 主函数,程序的入口
*
* 返回:整数,程序退出状态码
*/
int main(int argc, char const *argv[])
{
// 创建4个线程,代表4个售票员
pthread_t tids[4];
for (int i = 0; i < 4; i++)
{
if (pthread_create(&tids[i], NULL, &sell_ticket, NULL) != 0)
{
printf("线程创建失败!\n"); // 如果线程创建失败,输出错误信息
return 0;
}
}
sleep(10); // 阻塞主线程,等待所有子线程执行结束
for (int i = 0; i < 4; i++)
{
void *ans = NULL;
int flag = pthread_join(tids[i], &ans); // 等待线程结束
if (flag != 0)
{
printf("tid=%ld 等待失败!\n", tids[i]); // 如果等待线程失败,输出错误信息
return 0;
}
}
return 0; // 程序正常退出
}
运行结果:
其实从这里已经看出来,总共10张票,但是卖出了13张,这就是线程不同步造成的后果
0101使用宏定义初始化互斥锁
相关代码:
#include <stdio.h> // 包含标准输入输出库函数
#include <stdlib.h> // 包含常用工具函数如exit()
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <unistd.h> // 包含unistd.h头文件,提供对POSIX操作系统API的访问
int ticket_sum = 10; // 全局变量,表示总票数
// 声明一个互斥锁,用于同步线程对票数的访问
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
/**
* threadFun - 线程执行函数
* @arg: 线程参数,未使用
*
* 返回:NULL
*
* 功能:模拟售票过程,每个线程尝试卖出10张票。
*/
void *threadFun(void *arg)
{
printf("线程ID:%ld 已启动\n", pthread_self()); // 打印线程ID和启动信息
for (int i = 0; i < 10; i++)
{
// 尝试加锁,防止多个线程同时操作票数
if (pthread_mutex_lock(&myMutex) == 0)
{
if (ticket_sum > 0) // 如果还有票,则卖出一张
{
sleep(1); // 模拟售票过程需要的时间
printf("线程ID %ld 卖出第 %d 张票\n", pthread_self(), 10 - ticket_sum + 1);
--ticket_sum; // 票数减一
}
pthread_mutex_unlock(&myMutex); // 解锁
}
}
return NULL; // 线程执行完毕,返回NULL
}
/**
* main - 主函数
*
* 返回:返回码,0表示成功,-1表示失败
*
* 功能:创建四个售票线程,等待所有线程结束。
*/
int main(int argc, char const *argv[])
{
pthread_t threadID[4]; // 声明线程ID数组
for (int i = 0; i < 4; i++)
{
if (pthread_create(&threadID[i], NULL, threadFun, NULL) != 0)
{
perror("线程创建失败!"); // 如果创建失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
}
sleep(1); // 主线程休眠1秒,确保所有子线程都有机会运行
for (int i = 0; i < 4; i++)
{
if (pthread_join(threadID[i], NULL) != 0)
{
perror("线程等待失败!"); // 如果等待失败,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
}
return 0; // 返回0,表示程序执行成功
}
运行结果:
0102使用函数初始化互斥锁
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <pthread.h> // 引入多线程处理库
#include <time.h> // 引入时间处理库
// 声明一个互斥锁
pthread_mutex_t myMutex;
// 全局共享资源
int shareResource = 1;
/**
* increment - 一个线程函数,用于增加共享资源的值
* @arg: 线程参数,这里未使用
*
* 返回:NULL
*/
void *increment(void *arg)
{
struct timespec req, rem; // 用于nanosleep的结构体
for (int i = 0; i < 3; i++)
{
req.tv_sec = 1; // 设置睡眠时间为1秒
req.tv_nsec = 0L; // 纳秒部分设置为0
pthread_mutex_lock(&myMutex); // 加锁,保护共享资源
nanosleep(&req, &rem); // 线程睡眠1秒
printf("当前资源值为:%d\n", ++shareResource); // 输出资源值并自增
pthread_mutex_unlock(&myMutex); // 解锁
}
return NULL;
}
/**
* decrement - 一个线程函数,用于减少共享资源的值
* @arg: 线程参数,这里未使用
*
* 返回:NULL
*/
void *decrement(void *arg)
{
struct timespec req, rem; // 用于nanosleep的结构体
for (int i = 0; i < 3; i++)
{
req.tv_sec = 1; // 设置睡眠时间为1秒
req.tv_nsec = 0L; // 纳秒部分设置为0
pthread_mutex_lock(&myMutex); // 加锁,保护共享资源
nanosleep(&req, &rem); // 线程睡眠1秒
printf("当前资源值为:%d\n", --shareResource); // 输出资源值并自减
pthread_mutex_unlock(&myMutex); // 解锁
}
return NULL;
}
/**
* main - 主函数,程序的入口
* @argc: 命令行参数个数
* @argv: 命令行参数数组
*
* 返回:整数,程序退出状态码
*/
int main(int argc, char const *argv[])
{
pthread_mutex_init(&myMutex, NULL); // 初始化互斥锁
pthread_t incThread, decThread; // 声明两个线程ID变量
pthread_create(&incThread, NULL, increment, NULL); // 创建增加资源的线程
pthread_create(&decThread, NULL, decrement, NULL); // 创建减少资源的线程
pthread_join(incThread, NULL); // 等待增加资源的线程结束
pthread_join(decThread, NULL); // 等待减少资源的线程结束
printf("共享资源最后的值为:%d\n", shareResource); // 输出共享资源的最终值
pthread_mutex_destroy(&myMutex); // 销毁互斥锁
return 0; // 程序正常退出
}
运行结果:
从这里看到资源之是顺序递增或者递减的,中间没有跳跃式变动,这证明了互斥锁保证了线程同步。
0111信号量_二进制信号量
相关代码:
#include <stdio.h> // 包含标准输入输出库函数
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <semaphore.h> // 包含semaphore.h头文件,提供信号量操作函数
#include <unistd.h> // 包含unistd.h头文件,提供对POSIX操作系统API的访问
sem_t semaphore; // 声明一个信号量
int shared_resource = 0; // 全局变量,表示共享资源
/**
* threadFun - 线程执行函数
* @arg: 线程参数,传递线程编号
*
* 返回:NULL
*
* 功能:每个线程尝试三次增加共享资源的值,并打印出来。
*/
void *threadFun(void *arg)
{
for (int i = 0; i < 3; i++)
{
sem_wait(&semaphore); // P操作,信号量减一,如果信号量为0,则线程阻塞
++shared_resource; // 增加共享资源的值
printf("线程 %d:当前共享资源的值为:%d\n", *(int *)arg, shared_resource);
sem_post(&semaphore); // V操作,信号量加一,唤醒等待的线程
}
return NULL; // 线程执行完毕,返回NULL
}
/**
* main - 主函数
*
* 返回:返回码,0表示成功
*
* 功能:初始化信号量,创建两个线程,等待线程结束,销毁信号量。
*/
int main(int argc, char const *argv[])
{
pthread_t tid1, tid2; // 声明线程ID变量
sem_init(&semaphore, 0, 1); // 初始化信号量,初始值为1
int number1 = 1, number2 = 2; // 线程编号
pthread_create(&tid1, NULL, threadFun, (void *)&number1); // 创建第一个线程
pthread_create(&tid2, NULL, threadFun, (void *)&number2); // 创建第二个线程
pthread_join(tid1, NULL); // 等待第一个线程结束
pthread_join(tid2, NULL); // 等待第二个线程结束
sem_destroy(&semaphore); // 销毁信号量
return 0; // 返回0,表示程序执行成功
}
运行结果:
从这里看出,共享资源是顺序增长的,这代表了二进制信号量保证了线程同步
0112信号量_计数信号量
相关代码:
#include <unistd.h> // 引入Unix标准函数库,主要处理Unix标准系统调用
#include <stdio.h> // 引入标准输入输出库
#include <pthread.h> // 引入多线程处理库
#include <stdlib.h> // 引入标准库,主要包含了一些常用工具函数
#include <semaphore.h> // 引入信号量库
// 设置办理业务的人数
int num = 5;
// 创建信号量
sem_t sem;
/**
* get_service - 模拟客户办理业务的函数
* @arg: 线程参数,这里是客户的编号
*
* 返回:无实际意义,仅为了满足函数指针类型要求
*/
void *get_service(void *arg)
{
int id = *((int *)arg);
// 信号量成功“减1”后才能继续执行
if (sem_wait(&sem) == 0)
{
printf("---customer%d 正在办理业务\n", id);
sleep(2); // 模拟办理业务所需时间
printf("---customer%d 已办完业务\n\n", id);
// 信号量“加1”
sem_post(&sem);
}
return 0;
}
/**
* main - 主函数,程序的入口
*
* 返回:整数,程序退出状态码
*/
int main()
{
int flag, i, j;
// 创建5个线程代表5个人
pthread_t customer[5];
// 初始化信号量,允许同时有两个线程执行
sem_init(&sem, 0, 2);
for (i = 0; i < num; i++)
{
flag = pthread_create(&customer[i], NULL, get_service, &i);
if (flag != 0)
{
printf("线程创建失败!\n"); // 如果线程创建失败,输出错误信息
return 0;
}
else
{
printf("\ncustomer%d 来办理业务\n", i); // 输出客户到达信息
}
sleep(1); // 模拟客户到达的时间间隔
}
for (j = 0; j < num; j++)
{
flag = pthread_join(customer[j], NULL); // 等待线程结束
if (flag != 0)
{
printf("tid = %ld 等待失败!", customer[j]); // 如果等待线程失败,输出错误信息
return 0;
}
}
sem_destroy(&sem); // 销毁信号量
return 0; // 程序正常退出
}
运行结果:
从程序的运行结果可以看出,来办理业务的“客户”是一个个来的,每个过来的客户都会检查是否还有剩余的“信号量”,如果没有,则不会轮到下一个人办理业务。这样就保证了一个个“客户”按顺序办理业务。
0120条件变量与互斥锁
相关代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int item_to_consume = 0;
/**
* @brief 生产者线程函数,用于生产物品并通知消费者
*
* 这个函数是一个线程的执行主体,它不断地生产物品,并通知消费者线程。
* 函数使用一个无限循环来实现连续的生产过程。
*
* @param arg 线程参数,本函数未使用
* @return void* 线程返回值,本函数返回NULL
*/
void *producer(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
printf("生产者生产了一个物品,现在的物品总数为:%d\n", ++item_to_consume);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
/**
* @brief 消费者线程函数,用于消费物品
*
* 这个函数是一个线程的执行主体,它不断地尝试消费物品。
* 如果没有物品可消费,线程将等待生产者的通知。
*
* @param arg 线程参数,本函数未使用
* @return void* 线程返回值,本函数返回NULL
*/
void *consumer(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
// 如果没有物品,则等待生产者的通知
while (item_to_consume == 0)
{
pthread_cond_wait(&cond, &mutex);
}
printf("消费者消费了一个物品,现在物品总数为:%d\n", --item_to_consume);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
int main(int argc, char const *argv[])
{
pthread_t producer_threadID, consumer_threadID;
pthread_create(&producer_threadID, NULL, producer, NULL);
pthread_create(&consumer_threadID, NULL, consumer, NULL);
pthread_join(producer_threadID, NULL);
pthread_join(consumer_threadID, NULL);
return 0;
}
运行结果:
注意! 由于生成和消费都是进入死循环,此程序需要按下CTRL+C来强制结束
0130读写锁
注意! 此程序需要使用-std=gnu99
参数来进行编译,而非-std=c99
:
gcc -o main -std=gnu99 -Wall -pthread ./0130读写锁.c
相关代码:
#include <stdio.h> // 引入标准输入输出头文件
#include <pthread.h> // 引入多线程操作相关的头文件
#include <unistd.h> // 引入系统调用相关的头文件
#define COUNT 3 // 定义常量,表示读线程的数量
int x = 0; // 全局变量,用于线程间共享数据
pthread_rwlock_t rwlock; // 定义一个读写锁,用于控制对共享资源的访问
/**
* read_thread - 读锁线程的处理函数
* @arg: 线程参数,本例中未使用
*
* 该函数使用读锁访问共享资源,并打印读取到的数据。
* 循环执行,每秒执行一次。
*/
void *read_thread(void *arg)
{
printf("读锁线程 %ld 准备完毕。\n", pthread_self()); // 打印线程ID,表明线程准备就绪
while (1) // 无限循环
{
sleep(1); // 休眠1秒
pthread_rwlock_rdlock(&rwlock); // 加读锁
printf("读锁线程 %ld ,读取到的数据:%d\n", pthread_self(), x); // 打印线程ID和读取到的数据
pthread_rwlock_unlock(&rwlock); // 解锁
}
return NULL; // 线程函数返回NULL
}
/**
* write_thread - 写锁线程的处理函数
* @arg: 线程参数,本例中未使用
*
* 该函数使用写锁修改共享资源,并打印修改后的数据。
* 循环执行,每秒执行一次。
*/
void *write_thread(void *arg)
{
printf("写锁线程 %ld 准备完毕。\n", pthread_self()); // 打印线程ID,表明线程准备就绪
while (1) // 无限循环
{
sleep(1); // 休眠1秒
pthread_rwlock_wrlock(&rwlock); // 加写锁
printf("写锁线程 %ld ,当前数据为:%d\n", pthread_self(), ++x); // 打印线程ID和修改后的数据
pthread_rwlock_unlock(&rwlock); // 解锁
}
return NULL; // 线程函数返回NULL
}
int main(int argc, char const *argv[]) // 程序入口函数
{
pthread_rwlock_init(&rwlock, NULL); // 初始化读写锁
pthread_t readThreadID[COUNT]; // 定义读线程ID数组
for (int i = 0; i < COUNT; i++) // 循环创建读线程
{
pthread_create(&readThreadID[i], NULL, read_thread, NULL); // 创建读线程
}
pthread_t write_threadID; // 定义写线程ID
pthread_create(&write_threadID, NULL, write_thread, NULL); // 创建写线程
for (int i = 0; i < COUNT; i++) // 等待所有读线程结束
{
pthread_join(readThreadID[i], NULL); // 等待读线程结束
}
pthread_join(write_threadID, NULL); // 等待写线程结束
pthread_rwlock_destroy(&rwlock); // 销毁读写锁
return 0; // 主函数返回0,表明程序正常结束
}
运行结果:
0141死锁案例1_单互斥锁
相关代码:
#include <stdio.h> // 引入标准输入输出头文件
#include <pthread.h> // 引入多线程操作头文件
#include <unistd.h> // 引入系统调用头文件
// 初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/**
* 线程函数,用于演示互斥锁的使用
* @param arg 线程参数(本例中未使用)
* @return void* 线程返回值(本例中返回NULL)
*/
void *threadFun(void *arg)
{
// 尝试对互斥锁进行加锁,如果成功则打印信息
if (pthread_mutex_lock(&mutex) == 0)
{
printf("线程 %ld 已成功加锁!\n", pthread_self());
}
// 本例中线程不需要返回值,故返回NULL
return NULL;
}
/**
* 主函数,程序入口
* @param argc 命令行参数个数
* @param argv 命令行参数数组
* @return int 程序退出状态码
*/
int main(int argc, char const *argv[])
{
pthread_t threadID[3]; // 定义线程ID数组
for (int i = 0; i < 3; i++)
{
// 创建线程,并传递NULL作为线程参数
pthread_create(&threadID[i], NULL, threadFun, NULL);
// 打印线程创建完成信息
printf("线程 %ld 创建完成!\n", threadID[i]);
}
for (int i = 0; i < 3; i++)
{
// 等待线程执行完成
pthread_join(threadID[i], NULL);
// 打印线程执行完成信息
printf("线程 %ld 执行完成!\n", threadID[i]);
}
// 程序正常退出
return 0;
}
运行结果:
忘记解互斥锁将会造成死锁
0142死锁案例2_双互斥锁
相关代码:
#include <stdio.h> // 包含标准输入输出库函数
#include <pthread.h> // 包含pthread.h头文件,提供POSIX线程操作函数
#include <unistd.h> // 包含unistd.h头文件,提供对POSIX操作系统API的访问
// 声明并初始化两个互斥锁
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
/**
* threadFun1 - 线程1的执行函数
* @arg: 线程参数,未使用
*
* 返回:NULL
*
* 功能:线程1首先获取mutex1锁,然后等待2秒,再尝试获取mutex2锁。
*/
void *threadFun1(void *arg)
{
pthread_mutex_lock(&mutex1); // 尝试获取mutex1锁
printf("线程1 申请到 mutex1 锁!\n");
sleep(2); // 线程1休眠2秒
pthread_mutex_lock(&mutex2); // 尝试获取mutex2锁
printf("线程1 申请到 mutex2 锁!\n");
pthread_mutex_unlock(&mutex1); // 释放mutex1锁
printf("线程1释放了 mutex1 锁\n");
return NULL; // 线程执行完毕,返回NULL
}
/**
* threadFun2 - 线程2的执行函数
* @arg: 线程参数,未使用
*
* 返回:NULL
*
* 功能:线程2首先获取mutex2锁,然后等待2秒,再尝试获取mutex1锁。
*/
void *threadFun2(void *arg)
{
pthread_mutex_lock(&mutex2); // 尝试获取mutex2锁
printf("线程2 申请到 mutex2 锁!\n");
sleep(2); // 线程2休眠2秒
pthread_mutex_lock(&mutex1); // 尝试获取mutex1锁
printf("线程2 申请到 mutex1 锁!\n");
pthread_mutex_unlock(&mutex2); // 释放mutex2锁
printf("线程2释放了 mutex2 锁\n");
return NULL; // 线程执行完毕,返回NULL
}
/**
* main - 主函数
*
* 返回:返回码,0表示成功
*
* 功能:创建两个线程,等待线程结束。
*/
int main(int argc, char const *argv[])
{
pthread_t threadID1, threadID2; // 声明线程ID变量
pthread_create(&threadID1, NULL, threadFun1, NULL); // 创建线程1
pthread_create(&threadID2, NULL, threadFun2, NULL); // 创建线程2
pthread_join(threadID1, NULL); // 等待线程1结束
pthread_join(threadID2, NULL); // 等待线程2结束
return 0; // 返回0,表示程序执行成功
}
运行结果:
互相上锁将会造成死锁
0151线程分离1_pthread_detach函数
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <pthread.h> // 引入多线程处理库
#include <unistd.h> // 引入Unix标准函数库,主要处理Unix标准系统调用
/**
* threadFun - 子线程执行的函数
* @arg: 线程参数,这里未使用
*
* 返回:NULL
*/
void *threadFun(void *arg)
{
printf("子线程开始执行。\n");
sleep(2); // 模拟子线程执行时间
printf("子线程执行完毕。\n");
pthread_exit(NULL); // 子线程退出
// return NULL; // 这一行代码实际上不会执行,因为pthread_exit会终止线程
}
/**
* main - 主函数,程序的入口
* @argc: 命令行参数个数
* @argv: 命令行参数数组
*
* 返回:整数,程序退出状态码
*/
int main(int argc, char const *argv[])
{
pthread_t threadID; // 声明一个线程ID变量
pthread_create(&threadID, NULL, threadFun, NULL); // 创建一个新线程
pthread_detach(threadID); // 分离线程,线程资源将被立即释放
printf("线程已经分离,主线程将继续执行\n");
sleep(1); // 模拟主线程执行时间
printf("主线程执行完毕!\n");
return 0; // 程序正常退出
}
运行结果:
0152线程分离2_pthread_attr_init函数
相关代码:
#include <stdio.h> // 引入标准输入输出头文件
#include <pthread.h> // 引入多线程操作相关的头文件
#include <unistd.h> // 引入系统调用相关的头文件
/**
* threadFun - 子线程的执行函数
* @arg: 线程参数,本例中未使用
*
* 该函数用于演示子线程的执行过程,线程将睡眠2秒然后退出。
*/
void *threadFun(void *arg)
{
printf("子线程开始执行。\n"); // 打印信息,表明子线程开始执行
sleep(2); // 线程睡眠2秒
printf("子线程执行完毕。\n"); // 打印信息,表明子线程执行完毕
pthread_exit(NULL); // 子线程退出,释放资源
// return NULL; // 该行代码实际上不会执行,因为pthread_exit已经退出线程
}
int main(int argc, char const *argv[])
{
pthread_attr_t attr; // 定义线程属性变量
pthread_attr_init(&attr); // 初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程属性为分离状态
pthread_t threadID; // 定义线程ID变量
pthread_create(&threadID, &attr, threadFun, NULL); // 创建子线程
pthread_attr_destroy(&attr); // 销毁线程属性对象,释放资源
printf("线程已经分离,主线程将继续执行\n"); // 打印信息,表明线程已分离,主线程继续执行
sleep(1); // 主线程睡眠1秒
printf("主线程执行完毕!\n"); // 打印信息,表明主线程执行完毕
return 0; // 主函数返回0,表明程序正常结束
}
运行结果:
额外内容
此部分内容与多线程无关,仅作为拓展内容。
9011nanosleep延时函数1
相关代码:
#include <stdio.h> // 包含标准输入输出库函数
#include <time.h> // 包含time.h头文件,提供时间和日期操作函数
#include <errno.h> // 包含errno.h头文件,提供错误号定义
int main(int argc, char const *argv[])
{
struct timespec req, rem; // 定义timespec结构体变量,用于nanosleep函数
req.tv_sec = 1; // 设置秒数为1秒
req.tv_nsec = (long)(0.5 * 1000 * 1000 * 1000); // 设置纳秒数为0.5秒转换为纳秒
/**
* 如果 nanosleep 函数因为捕获到信号而提前返回,它会把剩余的延时时间填充至 rem 指向的结构体中,并返回-1。
* 如果延时正常完成,则返回0。
*/
while (nanosleep(&req, &rem) == -1)
{
if (errno == EINTR) // 如果错误号是EINTR,表示函数被信号中断
{
printf("nanosleep函数被正常中断\n"); // 打印被中断的信息
req = rem; // 更新req为剩余的时间
}
else
{
perror("执行nanosleep函数出现错误!"); // 如果是其他错误,打印错误信息
return -1; // 返回-1,表示程序执行失败
}
}
printf("延时结束,程序正常结束运行。\n"); // 延时完成,打印结束信息
return 0; // 返回0,表示程序执行成功
}
运行结果:
9012nanosleep延时函数2
相关代码:
#include <stdio.h> // 引入标准输入输出库
#include <time.h> // 引入时间处理库
/**
* main - 主函数,程序的入口
* @argc: 命令行参数个数
* @argv: 命令行参数数组
*
* 返回:整数,程序退出状态码
*/
int main(int argc, char const *argv[])
{
struct timespec req, rem; // 定义timespec结构体变量用于nanosleep
req.tv_sec = 1; // 设置睡眠秒数
req.tv_nsec = (long)0.5 * 1000 * 1000 * 1000; // 设置睡眠纳秒数,这里为0.5秒的纳秒数
printf("延时开始。\n"); // 输出提示信息
nanosleep(&req, &rem); // 执行睡眠操作,rem用于返回未睡眠完的时间(如果有)
printf("延时完毕。\n"); // 睡眠结束后输出提示信息
return 0; // 程序正常退出
}
运行结果: