实验理论参考:
1 一旦共享资源被互斥锁锁定,则其余线程想访问共享资源必须等待,直到锁被释放
2 使用normal属性的互斥锁,一旦发生重入逻辑,则阻塞,成为死锁
需要将属性改为recursive 成为可重入的,递归的
代码功能:
1 命令行传参 1 model=1
演示异步未上锁之乱序
演示count在数据竞态(Race Condition)下的错误值
2 命令行传参 2 model=2
演示使用互斥锁后 线程的执行顺序
演示count获得了正确值
演示未使用可重入锁对资源的重入访问
3 命令行传参 3 model=3
使用mutex的属性,开启递归锁(可重入锁),解决同一线程可重入问题
4 演示线程创建有序 执行无序 结束无序 回收有序 的深层逻辑
运行环境:
unix-like操作系统 gnu_c或兼容c库
编译后,命令行传参运行
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
// 全局变量 用于改变代码功能
int model = 0;
// 全局变量 用于演示同步异步
int count = 0;
// 全局都能看见 属于标准操作
pthread_mutex_t mutex_lock;
// 结构体 通常在全局声明 便于传参
struct args
{
FILE *arg_fp;
char arg_char;
};
void recursive(int a)
{
// 锁递归函数的全部逻辑
pthread_mutex_lock(&mutex_lock);
if (a != 0)
{
printf("%lu recursive %d\n", pthread_self(), a--);
recursive(a);
}
pthread_mutex_unlock(&mutex_lock);
}
// 每个线程的逻辑
void *put_char(void *p)
{
printf("%lu start \n", pthread_self());
int i;
char c;
FILE *fp;
// 要打印的大写字母赋值,打开的fp赋值
if (p != NULL)
{
struct args *ptr = (struct args *)p;
c = ptr->arg_char;
fp = ptr->arg_fp;
}
// 上锁,检查锁没锁上
if (model == 1)
{
}
else if (model == 2)
{
int r = pthread_mutex_lock(&mutex_lock);
if (r != 0)
{
fprintf(stderr, "pthread_mutex_lock:%s", strerror(r));
}
}
else
{
int r = pthread_mutex_lock(&mutex_lock);
if (r != 0)
{
fprintf(stderr, "pthread_mutex_lock:%s", strerror(r));
}
}
// 主逻辑就是每个线程 向文件f1输出2W次大写字母采用a+模式,全局变量增加2w次,每次+1
for (i = 0; i < 20000; i++)
{
fprintf(fp, "%c", c);
count++;
}
// 解锁
if (model == 1)
{
}
else if (model == 2)
{
pthread_mutex_unlock(&mutex_lock);
}
else
{
pthread_mutex_unlock(&mutex_lock);
}
printf("%lu end \n", pthread_self());
// 退出什么也不传递
pthread_exit(NULL);
}
void mutex_init()
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
if (model == 2)
{
// 此选项用于正常未修改的互斥锁
// 在执行递归任务时,会死锁,阻塞
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
}
else if (model == 3)
{
// 开启此选项,使用递归锁(可重入锁),在执行递归任务时,同一线程可以无限重入被锁定的函数
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
}
else
{
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
}
pthread_mutex_init(&mutex_lock, &attr);
}
// 初始化5个线程
void five_thread_init(FILE *fp)
{
//={0}是为了清垃圾
pthread_t tids[5] = {0};
// 这是传给每个线程的参数
char tid_argv[5] = {'A', 'B', 'C', 'D', 'E'};
// 将两个参数封装在struct里 传给每个线程
struct args arg_s[5] = {0};
for (size_t i = 0; i < 5; i++)
{
arg_s[i].arg_fp = fp;
arg_s[i].arg_char = tid_argv[i];
// 第一个参数是线程id,pthread_create函数返回就被存入tids[i]
// 第二个参数是线程属性,啥也不改填null
// 第三个参数是线程要执行的函数,此函数具有标准函数指针,必须符合
// 第四个参数是要传给这个执行函数的参数
pthread_create(&tids[i], NULL, put_char, (void *)&arg_s[i]);
}
for (size_t i = 0; i < 5; i++)
{
// 结束无序 回收有序
// 结束的线程变成僵尸线程等待有序回收
pthread_join(tids[i], NULL);
}
}
void is123(int argc, char *argv1)
{
if (argc != 2)
{
printf("Exit!\n");
_exit(EXIT_FAILURE);
}
else
{
if (strcmp(argv1, "1") == 0 ||
strcmp(argv1, "2") == 0 ||
strcmp(argv1, "3") == 0)
{
model = atoi(argv1);
}
else
{
printf("Exit!\n");
_exit(EXIT_FAILURE);
}
}
}
int main(int argc, char *argv[])
{
is123(argc, argv[1]);
// 进程开始 出发!
printf("%lu main start\n", pthread_self());
// 如果存在共享资源文件f1,则删除
if (access("f1", F_OK) == 0)
{
remove("f1");
}
// 初始化一个多类型的互斥锁(normal,recursive)
mutex_init();
// 打开f1文件,fprintf使用的是字符,便于观察,所以选用fopen
FILE *fp = fopen("f1", "a+");
// 初始化5个线程
five_thread_init(fp);
// 关闭f1
fclose(fp);
// 这句话用来展示同步异步的不同,理论10W,异步竞态条件下不满10W
printf("count_value: %d\n", count);
// pthread_join会阻塞到线程全跑完 在pthread_join之后复用mutex_lock现在锁是空闲的
// 运行到此处可以认为这是一把新锁
// 递归函数用于演示递归锁(可重入锁)
if (model == 1)
{
}
else if (model == 2)
{
recursive(10);
}
else
{
recursive(10);
}
// 玩完销毁,不留痕迹
pthread_mutex_destroy(&mutex_lock);
// 全剧终
printf("%lu main end\n", pthread_self());
return 0;
}