"释放CPU多核潜能,Linux线程技术助你一臂之力,创新无限!"#Linux系统编程之线程
- 前言
- 预备知识
- 一、 线程概述(与进程的区别及线程的优势)
- 1.1 进程与线程的联系图
- 1.2 进程与线程
- 1.3 使用线程的理由
- 二、 线程创建等待及退出
- 2.1 pthread_creat函数介绍
- 2.1.1 包含必要的头文件
- 2.1.2 定义线程函数
- 2.1.3 创建线程
- 2.1.4 等待线程结束(可选)
- 2.1.5 编译程序
- 2.1.6 pthread_create返回值
- 2.2 pthread_join函数介绍
- 2.3 pthread_self函数介绍
- 2.4 pthread_exit函数介绍
- 2.5 创建新线程并等待新线程执行完毕程序
- 2.5.1 创建新线程并等待新线程执行完毕程序代码
- 2.5.2 创建新线程并等待新线程执行完毕程序运行结果
- 2.6 创建,退出新线程并用等待新线程退出函数承接新线程退出码程序
- 2.6.1 创建,退出新线程并用等待新线程退出函数承接新线程退出码程序代码
- 2.6.2 创建,退出新线程并用等待新线程退出函数承接新线程退出码程序运行结果
- 2.7 将退出新线程输出的退出码改为字符串程序
- 2.7.1 将退出新线程输出的退出码改为字符串程序代码
- 2.7.2 将退出新线程输出的退出码改为字符串程序运行结果
- 三、 线程共享内存空间的代码验证
- 3.1 创建两个线程,让主线程+这两线程同时对一变量进行自增操作
- 3.1.1 程序代码
- 3.1.2 程序运行结果
- 3.2 让自增变量控制线程t1终止程序
- 3.2.1 程序代码
- 3.2.2 程序运行结果
- 四、 线程同步之互斥量加锁解锁
- 4.1 互斥量的介绍
- 4.1.1 基本概念
- 4.1.2 互斥量的作用
- 4.1.3 互斥量的实现原理
- 4.1.4 注意事项
- 4.2 相关API介绍
- 4.2.1 初始化互斥量
- 4.2.2 销毁互斥量
- 4.2.3 加锁和解锁互斥量
- 4.2.4 注意事项
- 4.3 对两新线程添加互斥锁
- 4.3.1 对两新线程添加互斥锁程序代码
- 4.3.2 对两新线程添加互斥锁程序运行结果
- 4.4 进一步验证互斥量的互斥性,在新线程t1中加循环控制输出
- 4.4.1 程序代码
- 4.4.2 程序运行结果
- 五、 互斥锁限制共享资源的访问
- 5.1 采用互斥锁控制新线程t1的结束
- 5.1.1 程序代码
- 5.1.2 程序运行结果
- 5.2 采用互斥锁控制进程退出
- 5.2.1 程序代码
- 5.2.2 分别写一个脚本和一个程序控制改=该程序运行5次验证运行结果
- 六、 什么情况造成互斥锁锁死
- 6.1 使用程序验证死锁现象
- 6.1.1 程序代码
- 6.1.2 程序运行结果
- 七、 线程条件控制实现线程的同步
- 7.1 线程条件的介绍
- 7.1.1 条件变量的基本概念
- 7.1.2 条件变量的使用场景
- 7.1.3 条件变量的操作
- 7.1.4 注意事项
- 7.2 线程条件操作相关API介绍
- 7.2.1 条件变量的初始化与销毁
- 7.2.2 条件等待与触发
- 7.2.3 注意事项
- 7.3 线程条件应用之重置t_data的值
- 7.3.1 程序代码
- 7.3.2 程序运行结果
- 7.4 编辑代码测试线程条件输出结果
- 7.4.1 被测程序代码
- 7.4.2 测试程序代码
- 7.4.2 测试输出命令
- 7.4.3 text_ret.txt文件中的内容
- 结束语
前言
欢迎探索Linux系统编程的线程世界,本篇博文将带您深入解析线程与进程的区别及线程的独特优势,揭示线程作为轻量级执行单元的并行力量。我们将从线程的创建、等待到退出全流程进行详尽介绍,并通过代码验证线程间共享内存空间的实际应用。进而,深入探讨线程同步的核心机制——互斥量,展示其如何加锁与解锁以保护共享资源,有效避免并发访问的冲突。同时,解析互斥锁锁死(死锁)的现象及其成因,提供实用的避免策略。最后,通过介绍线程条件控制,我们将展示如何利用条件变量实现线程间的精确同步,让您的程序在复杂的多线程环境中依然能够稳定高效地运行。期待这次探索之旅能让您对Linux系统编程中的线程有更深刻的理解和应用。希望这篇优化后的博文介绍能够吸引您的兴趣,让您在阅读的过程中收获满满。别忘了先赞再看,您的支持是我们不断前进的动力!
预备知识
一、C变量
二、基本输入输出
三、流程控制
四、函数
五、指针
六、字符串
七、结构体
八、联合体
九、Linux系统基本操作命令如mkdir,ls -l等。
十、Linux系统编程之进程的知识
如果以上知识不清楚,请自行学习后再来浏览。如果我有没例出的,请在评论区写一下。谢谢啦!
一、 线程概述(与进程的区别及线程的优势)
1.1 进程与线程的联系图
1.2 进程与线程
典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。
“进程——资源分配的最小单位,线程——程序执行的最小单位”
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1.3 使用线程的理由
进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
- 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
- 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
- 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
注:以上内容来自大佬"峰子_仰望阳光"
点击这里查看原文
二、 线程创建等待及退出
2.1 pthread_creat函数介绍
在Linux系统中,pthread_create
函数是 POSIX 线程(也称为 pthreads)库的一部分,用于创建一个新的线程。这个函数允许程序并行地执行多个任务。下面是如何在Linux系统中使用 pthread_create
函数的基本步骤:
2.1.1 包含必要的头文件
首先,你需要包含 pthread.h
头文件,它包含了 pthread_create
函数以及其他线程相关的函数和类型定义。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
2.1.2 定义线程函数
线程函数是线程执行时调用的函数。它必须接受一个 void*
类型的参数,并返回一个 void*
类型的值。
void* thread_function(void* arg) {
// 线程执行的代码
printf("Hello from thread!\n");
return NULL; // 通常返回NULL,但也可以返回指向数据的指针
}
2.1.3 创建线程
使用 pthread_create
函数来创建一个新线程。这个函数需要四个参数:一个指向 pthread_t
类型的指针(用于存储新线程的标识符),一个指向线程属性的指针(通常传递NULL以使用默认属性),一个指向线程函数的指针,以及一个传递给线程函数的参数。
pthread_t thread_id;
int ret = pthread_create(&thread_id, NULL, thread_function, NULL);
if (ret != 0) {
perror("pthread_create failed");
exit(EXIT_FAILURE);
}
2.1.4 等待线程结束(可选)
如果你需要等待线程完成其执行,可以使用 pthread_join
函数。这个函数会阻塞调用它的线程,直到指定的线程结束。
pthread_join(thread_id, NULL);
2.1.5 编译程序
编译使用 pthreads 的程序时,需要链接 pthread 库。使用 gcc 或 clang 时,可以通过添加 -lpthread
选项来实现。
gcc your_program.c -o your_program -lpthread
2.1.6 pthread_create返回值
若成功返回0,否则返回错误编号
2.2 pthread_join函数介绍
在Linux系统中,pthread_join
函数用于等待一个特定的线程终止。这个函数会阻塞调用它的线程,直到指定的线程结束执行。这对于确保线程之间的同步和防止程序在子线程完成其工作之前就退出是非常有用的。
pthread_join
函数的原型定义在 <pthread.h>
头文件中,其原型如下:
int pthread_join(pthread_t thread, void **retval);
thread
参数是目标线程的标识符,即你想要等待的线程的pthread_t
类型的值。retval
是一个指向void*
的指针的指针,用于接收目标线程通过其返回语句(如果有的话)返回的值。如果你不关心线程的返回值,可以将这个参数设置为NULL
。
如果函数成功,pthread_join
返回0
;如果发生错误,则返回错误编号。
2.3 pthread_self函数介绍
在Linux系统中,pthread_self
函数用于获取调用线程的线程标识符(pthread_t
类型)。这个函数在需要识别当前线程或者将当前线程的标识符传递给其他线程或函数时非常有用。
pthread_self
函数的原型定义在 <pthread.h>
头文件中,其原型如下:
pthread_t pthread_self(void);
该函数不接受任何参数,并返回一个 pthread_t
类型的值,该值唯一地标识了调用它的线程。
2.4 pthread_exit函数介绍
在Linux系统中,pthread_exit
函数用于终止调用它的线程。与进程退出时使用的 exit
函数不同,pthread_exit
仅影响调用它的线程,而不是整个进程。这允许程序中的其他线程继续执行。
pthread_exit
函数的原型定义在 <pthread.h>
头文件中,其原型如下:
void pthread_exit(void *retval);
retval
参数是一个指向void
的指针,它指向的值将被设置为线程的退出状态。这个值可以通过pthread_join
函数的第二个参数来检索(如果pthread_join
的第二个参数不是NULL
的话)。
2.5 创建新线程并等待新线程执行完毕程序
2.5.1 创建新线程并等待新线程执行完毕程序代码
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *funct(void *arg) 新线程执行函数
{
printf("t1: arg = %d\n",*(int *)arg); 输出创建新线程时传的参数值
printf("ti: ID = %ld\n",(unsigned long)pthread_self()); 输出新线程的标识符
}
int main()
{
pthread_t t1; 定义新线程的名字
int data = 100; 定义创建新线程时传输的参数和参数值
int ret; 定义判断是否成功建立新线程变量
ret = pthread_create(&t1,NULL,funct,(void *)&data); 创建名为t1的新线程
if(ret == 0) 判断是否成功建立新线程
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());输出主线程的标识符
}
else
{
puts("main: Creat new thread fail");
}
pthread_join(t1,NULL); 等待新线程执行完毕
return 0;
}
2.5.2 创建新线程并等待新线程执行完毕程序运行结果
如下图
2.6 创建,退出新线程并用等待新线程退出函数承接新线程退出码程序
2.6.1 创建,退出新线程并用等待新线程退出函数承接新线程退出码程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
//pthread_t pthread_self(void);
//int pthread_exit(void *rval_ptr);
void *funct(void *arg)
{
static int exitdata = 10; 必须采用静态变量定义新线程退出码
printf("t1: arg = %d\n",*(int *)arg);
printf("ti: ID = %ld\n",(unsigned long)pthread_self());
pthread_exit((void *)&exitdata); 退出新线程,并添加退出码
}
int main()
{
pthread_t t1;
int data = 100;
int ret;
int *pret = NULL; 定义用于承接退出码的整型指针变量pret
ret = pthread_create(&t1,NULL,funct,(void *)&data);
if(ret == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
else
{
puts("main: Creat new thread fail");
}
pthread_join(t1,(void **)&pret); 等待新线程退出,用pret变量承接退出码
printf("main: pret = %d\n",*pret); 输出退出码
return 0;
}
2.6.2 创建,退出新线程并用等待新线程退出函数承接新线程退出码程序运行结果
如下图
2.7 将退出新线程输出的退出码改为字符串程序
2.7.1 将退出新线程输出的退出码改为字符串程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
//pthread_t pthread_self(void);
//int pthread_exit(void *rval_ptr);
void *funct(void *arg)
{
static char *exitdata = "New thread quit"; 定义退出码的字符串
printf("t1: arg = %d\n",*(int *)arg);
printf("ti: ID = %ld\n",(unsigned long)pthread_self());
pthread_exit((void *)exitdata); 将字符串用作退出码
}
int main()
{
pthread_t t1;
int data = 100;
int ret;
char *pret = NULL; 定义用于承接字符串退出码的变量
ret = pthread_create(&t1,NULL,funct,(void *)&data);
if(ret == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
pthread_join(t1,(void **)&pret); 等待新线程退出,承接新线程退出码字符串
printf("main: pret = %s\n",pret); 输出退出码字符串
return 0;
}
2.7.2 将退出新线程输出的退出码改为字符串程序运行结果
如下图
三、 线程共享内存空间的代码验证
3.1 创建两个线程,让主线程+这两线程同时对一变量进行自增操作
3.1.1 程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int t_data = 0; 定义自增变量
void *func1(void *arg)
{
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t1: t_data = %d\n",t_data++); t1线程中对t_data自增
sleep(1); 睡眠1秒
}
}
void *func2(void *arg)
{
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t2: t_data = %d\n",t_data++); t2线程中对t_data自增
sleep(1); 睡眠1秒
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
ret1 = pthread_create(&t1,NULL,func1,(void *)&data); 创建进程t1
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data); 创建进程t2
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
while(1)
{
printf("main: t_data = %d\n",t_data++); 主线程对t——data自增
sleep(1); 睡眠1秒
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
3.1.2 程序运行结果
如下图
3.2 让自增变量控制线程t1终止程序
3.2.1 程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int t_data = 0;
void *func1(void *arg)
{
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t1: t_data = %d\n",t_data++);
sleep(1);
if(t_data == 3) 当t_data等于3时退出线程t1
{
pthread_exit(NULL);
}
}
}
void *func2(void *arg)
{
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t2: t_data = %d\n",t_data++);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
while(1)
{
printf("main: t_data = %d\n",t_data++);
sleep(1);
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
3.2.2 程序运行结果
如下图
四、 线程同步之互斥量加锁解锁
4.1 互斥量的介绍
在Linux系统中,线程的互斥量(Mutex,全称Mutual Exclusion)是一种同步机制,用于在多线程环境中保护临界资源,确保同一时间只有一个线程可以访问该资源,从而避免数据竞争和不一致性。以下是关于Linux系统中线程互斥量的详细介绍:
4.1.1 基本概念
- 互斥:指某一资源同时只允许一个访问者(线程或进程)对其进行访问,具有唯一性和排他性。互斥量就是实现这种互斥访问的一种机制。
- 临界资源:在多线程或多进程环境下,不允许同时被多个执行流访问的资源。这些资源的特点在于它们的状态可能因并发访问而变得不确定或引发错误。
- 临界区:访问临界资源的代码区域。当一个线程进入临界区时,它正在访问或操作临界资源,此时应确保其他线程不能同时进入这一相同的代码区域。
4.1.2 互斥量的作用
- 保护临界资源:通过互斥量,可以确保在任一时刻,只有一个线程能够访问临界资源,从而避免数据竞争和冲突。
- 实现线程同步:在互斥的基础上,通过其他机制(如条件变量、信号量等)可以实现线程间的有序访问,进一步保证程序的正确性和稳定性。
4.1.3 互斥量的实现原理
- 状态表示:互斥量通常有两种状态——锁定状态和空闲状态。锁定状态表示当前互斥量已被某个线程占用,空闲状态则表示互斥量当前未被占用。
- 操作接口:Linux提供了一系列API来操作互斥量,包括初始化(
pthread_mutex_init
)、加锁(pthread_mutex_lock
)、尝试加锁(pthread_mutex_trylock
)、解锁(pthread_mutex_unlock
)和销毁(pthread_mutex_destroy
)等。 - 加锁与解锁:线程在访问临界资源前必须先对互斥量进行加锁操作,访问完成后必须执行解锁操作。如果互斥量已被锁定,则尝试加锁的线程将被阻塞,直到互斥量被释放。
4.1.4 注意事项
- 避免死锁:死锁主要发生在有多个依赖锁存在时,一个线程试图以与另一个线程相反的顺序锁住互斥量。为了避免死锁,应确保所有线程对互斥量的加锁顺序一致,并尽量缩短锁的持有时间。
- 性能考虑:频繁的加锁和解锁操作可能会对程序性能产生影响。因此,在设计程序时应尽量减小临界区的大小,并考虑使用其他同步机制(如读写锁、信号量等)来优化性能。
4.2 相关API介绍
在Linux系统中,操作线程互斥量的相关API主要定义在<pthread.h>头文件中。这些API提供了创建、销毁、加锁、解锁互斥量等功能,以确保多线程环境下对共享资源的互斥访问。以下是这些API的详细介绍:
4.2.1 初始化互斥量
- 静态初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
这种方式适用于全局或静态分配的互斥量,且不需要在程序结束时销毁。
- 动态初始化:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
mutex
:指向要初始化的互斥量的指针。attr
:用于指定互斥量的属性,通常设置为NULL
以使用默认属性。返回值
:成功时返回0,失败时返回错误码。
4.2.2 销毁互斥量
-
销毁互斥量:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex
:指向要销毁的互斥量的指针。返回值
:成功时返回0,失败时返回错误码。
注意
:使用PTHREAD_MUTEX_INITIALIZER
初始化的互斥量不需要销毁;不要销毁一个已经加锁的互斥量;确保销毁后不会有线程再尝试加锁。
4.2.3 加锁和解锁互斥量
-
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex
:指向要加锁的互斥量的指针。返回值
:成功时返回0,失败时返回错误码。
如果互斥量已被其他线程锁定,调用线程将被阻塞,直到互斥量被解锁。
-
非阻塞加锁:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
mutex
:指向要尝试加锁的互斥量的指针。返回值
:成功时返回0,如果互斥量已被锁定则返回EBUSY
,失败时返回其他错误码。
这个函数尝试对互斥量加锁,但如果互斥量已被锁定,则不会阻塞调用线程,而是立即返回一个错误码。
-
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex
:指向要解锁的互斥量的指针。返回值
:成功时返回0,失败时返回错误码。
这个函数释放互斥量上的锁,允许其他线程加锁。
4.2.4 注意事项
- 在使用互斥量时,应确保每个加锁操作都有对应的解锁操作,以避免死锁。
- 尽量避免在持有锁的情况下执行耗时操作,以减少线程阻塞时间。
- 在多线程程序中,合理设计锁的范围和顺序,以避免潜在的死锁问题。
这些API为Linux系统下的多线程编程提供了强大的同步机制,确保了线程间对共享资源的互斥访问。
4.3 对两新线程添加互斥锁
4.3.1 对两新线程添加互斥锁程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int t_data = 0;
pthread_mutex_t mutex; 静态创建全局互斥量mutex
void *func1(void *arg)
{
//int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock(&mutex); 对线程1进行互斥量加锁
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_unlock(&mutex); 对线程1进行互斥量解锁
}
void *func2(void *arg)
{
//int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock(&mutex); 对线程2进行互斥量加锁
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_unlock(&mutex); 对线程2进行互斥量解锁
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_init(&mutex,NULL); 动态创建互斥量
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
printf("main: t_data = %d\n",t_data++);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
//int pthread_mutex_destroy(pthread_mutex_t mutex);
pthread_mutex_destroy(&mutex); 销毁互斥量
return 0;
}
4.3.2 对两新线程添加互斥锁程序运行结果
如下图
4.4 进一步验证互斥量的互斥性,在新线程t1中加循环控制输出
4.4.1 程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int t_data = 0;
pthread_mutex_t mutex;
void *func1(void *arg)
{
int i;
//int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock(&mutex);
for(i=0; i<5; i++) 在新线程t1中添加for循环输出5次
{
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
sleep(1); 输出一次间隔1秒
}
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
//int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock(&mutex);
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_init(&mutex,NULL);
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
printf("main: t_data = %d\n",t_data++);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
//int pthread_mutex_destroy(pthread_mutex_t mutex);
pthread_mutex_destroy(&mutex);
return 0;
}
4.4.2 程序运行结果
如下图
五、 互斥锁限制共享资源的访问
5.1 采用互斥锁控制新线程t1的结束
5.1.1 程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg)
pthread_mutex_t mutex;
int t_data = 0;
void *func1(void *arg)
{
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
pthread_mutex_lock(&mutex); 对互斥量进行上锁
while(1)
{
printf("t1: t_data = %d\n",t_data++);
sleep(1);
if(t_data == 3)
{
printf("t1 quit==============================\n");
pthread_mutex_unlock(&mutex); 当t_data等于三时才对互斥量解锁解锁
pthread_exit(NULL);
}
}
}
void *func2(void *arg)
{
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t2: t_data = %d\n",t_data);
每当要对t_data相加时,就要进行一次上锁和解锁
pthread_mutex_lock(&mutex);
t_data++;
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
pthread_mutex_init(&mutex,NULL);
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
while(1)
{
printf("main: t_data = %d\n",t_data); 主线程只输出t_data的值
sleep(1);
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
原理分析
在互斥锁的控制下,如果首先启动新线程t1,则t1将执行直至t_data自增到3,此时才释放锁并退出。之后,新线程t2得以执行。相反,若先启动t2,由于互斥锁的存在,t2将仅能对t_data进行一次自增操作,随后t2会等待t1获取锁继续其操作,直到t1完成t_data自增至3并释放锁后,t2(如果仍在等待)或其他任何线程(若已存在)才能继续执行。这样的机制有效地确保了t_data作为共享资源的安全访问与更新,同时也精确控制了新线程t1的退出条件。
5.1.2 程序运行结果
如下图
5.2 采用互斥锁控制进程退出
5.2.1 程序代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg)
pthread_mutex_t mutex;
int t_data = 0;
void *func1(void *arg)
{
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
pthread_mutex_lock(&mutex);
while(1)
{
printf("t1: t_data = %d\n",t_data++);
sleep(1);
if(t_data == 3)
{
printf("t1 quit==============================\n");
pthread_mutex_unlock(&mutex);
exit(0); 当t_data的值为3时退出进程
}
}
}
void *func2(void *arg)
{
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t2: t_data = %d\n",t_data);
pthread_mutex_lock(&mutex);
t_data++;
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
pthread_mutex_init(&mutex,NULL);
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
while(1)
{
printf("main: t_data = %d\n",t_data);
sleep(1);
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
原理分析
在互斥锁的控制下,如果首先启动新线程t1,则t1将执行直至t_data自增到3,此时才释放锁并退出进程。相反,若先启动t2,由于互斥锁的存在,t2将仅能对t_data进行一次自增操作,随后t2会等待t1获取锁继续其操作,直到t1完成t_data自增至3并释放锁后进程退出。这样的机制有效地确保了t_data作为共享资源的安全访问与更新,同时也精确控制了新线程t1的退出条件。
5.2.2 分别写一个脚本和一个程序控制改=该程序运行5次验证运行结果
一、 脚本文件
文件名:text.th
-------------------------------------------------------------------------
文件内容
-------------------------------------------------------------------------
./TP2
./TP2
./TP2
./TP2
./TP2
运行5次可执行文件TP2
-------------------------------------------------------------------------
如何编译脚本文件
用命令 chmod +x 脚本名进行编译
-------------------------------------------------------------------------
如何运行该脚本
./text.th 即可
chmod +x 命令详解
在Linux系统中,chmod
命令用于改变文件或目录的访问权限。+x
选项用于给文件或目录添加执行(execute)权限。具体使用时,你需要指定文件或目录的路径以及使用chmod
命令。
- 基本语法
chmod +x 文件名或目录名
这条命令会给指定的文件或目录的所有用户(文件所有者、所属组的成员、其他用户)添加执行权限。
- 示例
- 给文件添加执行权限
假设你有一个名为script.sh
的脚本文件,并希望为其添加执行权限,以便可以运行它,你可以使用以下命令:
chmod +x script.sh
执行此命令后,script.sh
文件将被赋予执行权限,你就可以通过./script.sh
来运行它了。
- 给目录添加执行权限
目录的执行权限与文件不同。对于目录,执行权限允许用户进入该目录(即cd
到该目录)。假设你有一个名为mydir
的目录,并希望为所有用户添加进入该目录的权限,你可以使用:
chmod +x mydir
但是,请注意,仅仅给目录添加执行权限并不足以让你在目录中查看或修改文件(这取决于文件的权限和用户的身份)。
- 更细粒度的权限控制
chmod
命令还允许你更细粒度地控制权限,比如仅给文件所有者添加执行权限,或同时给文件所有者和所属组添加执行权限。这可以通过使用符号模式(如u+x
, g+x
, o+x
)或数字模式(如755
)来实现。
-
符号模式:
u+x
:给文件所有者添加执行权限。g+x
:给所属组成员添加执行权限。o+x
:给其他用户添加执行权限。a+x
:给所有用户(所有者、所属组、其他用户)添加执行权限(等同于u+x g+x o+x
)。
-
数字模式(权限值按照所有者、所属组、其他用户的顺序排列,每个用户有读、写、执行三种权限,分别对应4、2、1的数值):
755
:给所有者完全权限(读4+写2+执行1=7),给所属组和其他用户读和执行权限(5=4+1)。
例如,仅给文件所有者添加执行权限,可以使用:
chmod u+x 文件名
或者,使用数字模式为所有用户设置特定的权限(如所有者完全权限,所属组和其他用户读和执行权限):
chmod 755 文件名
二、 程序代码
#include <stdio.h>
int main()
{
int i;
for(i=0; i<5; i++) 5次使用system函数让TP2执行5次
{
system("./TP2");
}
return 0;
}
脚本程序运行结果
CLC@Embed_Learn:~/Linux_System_Programming/04_Linux_thread/04_Mutex_lock_unlock_share_reaources$ ./run
main: Creat new thread successful
main: ID = 140275765892864
t1: arg = 100
t1: ID = 140275757606656
t1: t_data = 0
t2: arg = 100
t2: ID = 140275749213952
t2: t_data = 1
main: Creat new thread successful
main: ID = 140275765892864
main: t_data = 1
t1: t_data = 1
main: t_data = 2
main: t_data = 2
t1: t_data = 2
t1 quit==============================
main: t_data = 3
main: Creat new thread successful
main: ID = 139830291961600
t1: arg = 100
t1: ID = 139830283675392
t1: t_data = 0
main: Creat new thread successful
main: ID = 139830291961600
main: t_data = 1
t2: arg = 100
t2: ID = 139830275282688
t2: t_data = 1
main: t_data = 1
t1: t_data = 1
main: t_data = 2
t1: t_data = 2
main: t_data = 3
t1 quit==============================
main: Creat new thread successful
main: ID = 139952376575744
main: Creat new thread successful
main: ID = 139952376575744
main: t_data = 0
t1: arg = 100
t1: ID = 139952368289536
t1: t_data = 0
t2: arg = 100
t2: ID = 139952359896832
t2: t_data = 1
t1: t_data = 1
main: t_data = 2
t1: t_data = 2
main: t_data = 3
t1 quit==============================
main: Creat new thread successful
main: ID = 140301735687936
t1: arg = 100
t1: ID = 140301727401728
t1: t_data = 0
main: Creat new thread successful
main: ID = 140301735687936
main: t_data = 1
t2: arg = 100
t2: ID = 140301719009024
t2: t_data = 1
main: t_data = 1
t1: t_data = 1
t1: t_data = 2
main: t_data = 3
t1 quit==============================
main: Creat new thread successful
main: ID = 140237602895616
t1: arg = 100
t1: ID = 140237594609408
t1: t_data = 0
main: Creat new thread successful
main: ID = 140237602895616
main: t_data = 1
t2: arg = 100
t2: ID = 140237586216704
t2: t_data = 1
main: t_data = 1
t1: t_data = 1
t1: t_data = 2
main: t_data = 3
t1 quit==============================
main: t_data = 3
main: t_data = 3
运行结果分析
无论是脚本还是程序,我都运行了五次《采用互斥锁控制进程退出程序》的实例,因此,实际上我只运行了该程序的五次迭代。
从上述运行结果中可以清晰看出,程序被成功执行了五次,且每次都是在t_data的值达到3时正确退出,这充分验证了程序的准确性和稳定性。
六、 什么情况造成互斥锁锁死
在Linux系统线程中,互斥锁(Mutex)锁死通常指的是死锁(Deadlock)的一种情况,即两个或更多个线程在执行过程中,由于相互持有对方需要的资源(在这种情况下是互斥锁)而导致的一种阻塞现象,若无外力作用,这些线程都无法继续执行下去。
死锁通常发生在多线程或多进程环境中,尤其是在共享资源(如互斥锁保护的共享数据)被多个线程或进程访问时。
互斥锁锁死(或死锁)的发生通常需要满足以下四个必要条件(Coffman条件):
- 互斥条件:资源是互斥的,即一次只能被一个线程(或进程)使用。
- 请求与保持条件:一个线程(或进程)因为请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程(或进程)已获得的资源,在未使用完之前,不能由其他线程(或进程)强行剥夺。
- 循环等待条件:系统中若干线程(或进程)形成一种头尾相接的循环等待资源关系。
6.1 使用程序验证死锁现象
6.1.1 程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int t_data = 0;
pthread_mutex_t mutex;
pthread_mutex_t mutex2;
void *func1(void *arg)
{
//int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock(&mutex); 1.线程t1先拿到第一把锁
sleep(1);
pthread_mutex_lock(&mutex2); 3.这时线程t1想去拿t2拿到的第二把锁,拿不到卡死
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
//int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_lock(&mutex2); 2.然后线程t2拿到第二把锁
sleep(1);
pthread_mutex_lock(&mutex); 4.最后线程t2也想拿线程t1拿到的第一把锁,那不到卡死。最后导致整个程序卡死。
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
//int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
//int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_init(&mutex,NULL);
pthread_mutex_init(&mutex2,NULL);
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
puts("main: Creat new thread successful");
printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
printf("main: t_data = %d\n",t_data++);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
//int pthread_mutex_destroy(pthread_mutex_t mutex);
pthread_mutex_destroy(&mutex);
pthread_mutex_destroy(&mutex2);
return 0;
}
6.1.2 程序运行结果
如下图
七、 线程条件控制实现线程的同步
7.1 线程条件的介绍
在Linux系统下,线程的条件通常指的是线程同步机制中的一个重要概念,它涉及到线程间的协作和等待特定事件或条件的发生。具体来说,条件变量(Condition Variables)是实现这一机制的关键工具。以下是对Linux系统下线程条件的详细介绍:
7.1.1 条件变量的基本概念
条件变量是线程同步的一种机制,它允许一个或多个线程等待某个特定条件的发生。条件变量与互斥锁(Mutex)一起使用,以确保线程在访问共享数据时的同步和互斥性。条件变量本身并不包含任何条件,而是由线程通过互斥锁保护的共享数据来设定或检测条件。
7.1.2 条件变量的使用场景
条件变量主要用于以下场景
:
- 线程等待:当一个线程需要等待某个条件成立时(如数据准备好、资源可用等),它可以使用条件变量进入等待状态,并释放与之关联的互斥锁。
- 条件触发:当另一个线程改变了条件并希望唤醒等待的线程时,它会使用条件变量的信号(signal)或广播(broadcast)功能来唤醒一个或多个等待的线程。
7.1.3 条件变量的操作
在Linux中,条件变量的操作通常涉及以下几个步骤
:
- 初始化:使用
pthread_cond_init
函数初始化条件变量。如果条件变量是静态分配的,也可以使用PTHREAD_COND_INITIALIZER
常量进行初始化。 - 等待:线程使用
pthread_cond_wait
或pthread_cond_timedwait
函数等待条件变量的条件成立。这些函数会先解锁互斥锁,然后让线程进入休眠状态,直到条件被触发。 - 信号/广播:当条件满足时,另一个线程使用
pthread_cond_signal
或pthread_cond_broadcast
函数来唤醒一个或所有等待该条件变量的线程。 - 销毁:当条件变量不再需要时,应使用
pthread_cond_destroy
函数进行销毁。
7.1.4 注意事项
- 互斥锁保护:在修改条件或访问被条件变量保护的共享数据时,必须使用互斥锁来确保数据的一致性和同步性。
- 避免虚假唤醒:由于条件变量的实现可能会导致线程被虚假唤醒(即没有条件满足的情况下线程被唤醒),因此在使用条件变量时,通常需要在循环中检查条件是否真正满足。
- 性能考虑:条件变量的使用可能会对系统性能产生影响,尤其是在高并发场景下。因此,在设计多线程程序时,需要仔细考虑线程间的同步机制和资源竞争问题。
7.2 线程条件操作相关API介绍
7.2.1 条件变量的初始化与销毁
-
初始化
- 函数原型:
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
- 参数说明:
cv
:指向要初始化的条件变量对象的指针。cattr
:指向条件变量属性的指针,通常设置为NULL以使用默认属性。
- 返回值:成功时返回0,失败时返回错误码。
- 函数原型:
-
销毁
- 函数原型:
int pthread_cond_destroy(pthread_cond_t *cv);
- 参数说明:
cv
指向要销毁的条件变量对象的指针。 - 返回值:成功时返回0,失败时返回错误码。
- 函数原型:
7.2.2 条件等待与触发
-
条件等待
- 函数原型:
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
- 参数说明:
cv
:指向条件变量的指针。mutex
:指向互斥锁的指针,该互斥锁用于保护与条件变量相关联的共享数据。
- 操作描述:该函数将当前线程置于等待条件变量的阻塞状态,并自动释放与之关联的互斥锁。当条件变量被触发时,线程被唤醒并重新获得互斥锁。
- 函数原型:
-
条件触发(单个线程)
- 函数原型:
int pthread_cond_signal(pthread_cond_t *cv);
- 参数说明:
cv
指向条件变量的指针。 - 操作描述:该函数唤醒在指定条件变量上等待的线程中的一个(如果有的话)。如果没有线程在等待,该函数不执行任何操作。
- 函数原型:
-
条件广播(所有线程)
- 函数原型:
int pthread_cond_broadcast(pthread_cond_t *cv);
- 参数说明:
cv
指向条件变量的指针。 - 操作描述:该函数唤醒在指定条件变量上等待的所有线程。
- 函数原型:
7.2.3 注意事项
- 互斥锁保护:在修改条件或访问被条件变量保护的共享数据时,必须使用互斥锁来确保数据的一致性和同步性。
- 虚假唤醒:由于条件变量的实现可能会导致线程被虚假唤醒(即没有条件满足的情况下线程被唤醒),因此在使用条件变量时,通常需要在循环中检查条件是否真正满足。
- 性能考虑:条件变量的使用可能会对系统性能产生影响,尤其是在高并发场景下。因此,在设计多线程程序时,需要仔细考虑线程间的同步机制和资源竞争问题。
7.3 线程条件应用之重置t_data的值
7.3.1 程序代码
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg)
pthread_mutex_t mutex;
pthread_cond_t cond; 创建条件变量cond
int t_data = 0;
void *func1(void *arg)
{
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
pthread_mutex_lock(&mutex);
while(1)
{
pthread_cond_wait(&cond,&mutex); 线程等待条件触发
printf("t1: t_data = %d\n",t_data);
printf("t1 run==============================\n");
t_data = 0; 条件触发后对t_data重置
pthread_mutex_unlock(&mutex);
}
}
void *func2(void *arg)
{
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t2: t_data = %d\n",t_data);
pthread_mutex_lock(&mutex);
t_data++;
if(t_data == 3)
{
pthread_cond_signal(&cond); 当t_data等于3时触发条件
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL); 初识化条件
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
// puts("main: Creat new thread successful");
// printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
// puts("main: Creat new thread successful");
// printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond); 销毁条件
return 0;
}
原理分析
在线程t2中,对共享变量t_data执行自增操作。当t_data的值递增至3时,触发一个条件变量,使得正等待该条件的线程t1被唤醒并继续执行。随后,线程t1将重置t_data的值为0。这样的机制确保了线程间的同步与数据的一致性。
7.3.2 程序运行结果
如下图
7.4 编辑代码测试线程条件输出结果
7.4.1 被测程序代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg)
pthread_mutex_t mutex;
pthread_cond_t cond;
int t_data = 0;
void *func1(void *arg)
{
static int cnt = 0;
printf("t1: arg = %d\n",*(int *)arg);
printf("t1: ID = %ld\n",(unsigned long)pthread_self());
pthread_mutex_lock(&mutex);
while(1)
{
pthread_cond_wait(&cond,&mutex);
printf("t1: t_data = %d\n",t_data);
printf("t1 run==============================\n");
t_data = 0;
pthread_mutex_unlock(&mutex);
if(cnt++ == 2) 对t_data重置两次后退出该进程
{
exit(0);
}
}
}
void *func2(void *arg)
{
printf("t2: arg = %d\n",*(int *)arg);
printf("t2: ID = %ld\n",(unsigned long)pthread_self());
while(1)
{
printf("t2: t_data = %d\n",t_data);
pthread_mutex_lock(&mutex);
t_data++;
if(t_data == 3)
{
pthread_cond_signal(&cond);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
int data = 100;
int ret1;
int ret2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
ret1 = pthread_create(&t1,NULL,func1,(void *)&data);
if(ret1 == 0)
{
// puts("main: Creat new thread successful");
// printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
ret2 = pthread_create(&t2,NULL,func2,(void *)&data);
if(ret2 == 0)
{
// puts("main: Creat new thread successful");
// printf("main: ID = %ld\n",(unsigned long)pthread_self());
}
else
{
puts("main: Creat new thread fail");
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
7.4.2 测试程序代码
#include <stdio.h>
int main(int argc,char **argv)
{
int i;
int time = atoi(argv[1]);
for(i=0; i<time; i++)
{
system("./TC2");
}
printf("end\n");
return 0;
}
键入次数,使该程序对TC2执行键入次数
7.4.2 测试输出命令
测试结果输出到text_ret.txt文件中
-------------------------------------------------------------------------
./text 10 >>text_ret.txt &
将后台运行测试程序,并将输出结果输出到文件text_ret.txt中。
符号'>>' 输出到
符号'&' 后台运行
-------------------------------------------------------------------------
运行后的输出结果
CLC@Embed_Learn:~/Linux_System_Programming/04_Linux_thread/06_Thread_condition$ ./text 10 >>text_ret.txt &
[1] 5739 输出测试程序的进程标识符
7.4.3 text_ret.txt文件中的内容
t1: arg = 100
t1: ID = 139954086307584
t2: arg = 100
t2: ID = 139954077914880
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t1: arg = 100
t1: ID = 140549605926656
t2: arg = 100
t2: ID = 140549597533952
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t1: arg = 100
t1: ID = 140213932185344
t2: arg = 100
t2: ID = 140213923792640
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t1: arg = 100
t1: ID = 140154361214720
t2: arg = 100
t2: ID = 140154352822016
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t1: arg = 100
t1: ID = 140725302425344
t2: arg = 100
t2: ID = 140725294032640
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: arg = 100
t2: ID = 139646131349248
t2: t_data = 0
t1: arg = 100
t1: ID = 139646139741952
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t1: arg = 100
t1: ID = 139831455942400
t2: arg = 100
t2: ID = 139831447549696
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t1: arg = 100
t1: ID = 139886962341632
t2: arg = 100
t2: ID = 139886953948928
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
t2: t_data = 0
t2: t_data = 1
t2: t_data = 2
t1: t_data = 3
t1 run==============================
结束语
非常感谢您的耐心阅读!在您即将离开之际,如果您觉得内容有所收获或启发,不妨动动手指,点个赞再走,这将是对我莫大的鼓励和支持。衷心感谢您的点赞与关注,期待未来能继续为您带来更多有价值的内容!谢谢您!