“释放CPU多核潜能,Linux线程技术助你一臂之力,创新无限!“#Linux系统编程之线程

news2024/11/15 15:32:57

"释放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 文件名或目录名

  这条命令会给指定的文件或目录的所有用户(文件所有者、所属组的成员、其他用户)添加执行权限。

  • 示例
  1. 给文件添加执行权限

  假设你有一个名为script.sh的脚本文件,并希望为其添加执行权限,以便可以运行它,你可以使用以下命令:

chmod +x script.sh

  执行此命令后,script.sh文件将被赋予执行权限,你就可以通过./script.sh来运行它了。

  1. 给目录添加执行权限

  目录的执行权限与文件不同。对于目录,执行权限允许用户进入该目录(即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条件):

  1. 互斥条件:资源是互斥的,即一次只能被一个线程(或进程)使用。
  2. 请求与保持条件:一个线程(或进程)因为请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程(或进程)已获得的资源,在未使用完之前,不能由其他线程(或进程)强行剥夺。
  4. 循环等待条件:系统中若干线程(或进程)形成一种头尾相接的循环等待资源关系。

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 条件变量的使用场景

  条件变量主要用于以下场景

  1. 线程等待:当一个线程需要等待某个条件成立时(如数据准备好、资源可用等),它可以使用条件变量进入等待状态,并释放与之关联的互斥锁。
  2. 条件触发:当另一个线程改变了条件并希望唤醒等待的线程时,它会使用条件变量的信号(signal)或广播(broadcast)功能来唤醒一个或多个等待的线程。

7.1.3 条件变量的操作

  在Linux中,条件变量的操作通常涉及以下几个步骤

  1. 初始化:使用pthread_cond_init函数初始化条件变量。如果条件变量是静态分配的,也可以使用PTHREAD_COND_INITIALIZER常量进行初始化。
  2. 等待:线程使用pthread_cond_waitpthread_cond_timedwait函数等待条件变量的条件成立。这些函数会先解锁互斥锁,然后让线程进入休眠状态,直到条件被触发。
  3. 信号/广播:当条件满足时,另一个线程使用pthread_cond_signalpthread_cond_broadcast函数来唤醒一个或所有等待该条件变量的线程。
  4. 销毁:当条件变量不再需要时,应使用pthread_cond_destroy函数进行销毁。
7.1.4 注意事项
  1. 互斥锁保护:在修改条件或访问被条件变量保护的共享数据时,必须使用互斥锁来确保数据的一致性和同步性。
  2. 避免虚假唤醒:由于条件变量的实现可能会导致线程被虚假唤醒(即没有条件满足的情况下线程被唤醒),因此在使用条件变量时,通常需要在循环中检查条件是否真正满足。
  3. 性能考虑:条件变量的使用可能会对系统性能产生影响,尤其是在高并发场景下。因此,在设计多线程程序时,需要仔细考虑线程间的同步机制和资源竞争问题。

7.2 线程条件操作相关API介绍

7.2.1 条件变量的初始化与销毁
  1. 初始化

    • 函数原型int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
    • 参数说明
      • cv:指向要初始化的条件变量对象的指针。
      • cattr:指向条件变量属性的指针,通常设置为NULL以使用默认属性。
    • 返回值:成功时返回0,失败时返回错误码。
  2. 销毁

    • 函数原型int pthread_cond_destroy(pthread_cond_t *cv);
    • 参数说明cv指向要销毁的条件变量对象的指针。
    • 返回值:成功时返回0,失败时返回错误码。
7.2.2 条件等待与触发
  1. 条件等待

    • 函数原型int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
    • 参数说明
      • cv:指向条件变量的指针。
      • mutex:指向互斥锁的指针,该互斥锁用于保护与条件变量相关联的共享数据。
    • 操作描述:该函数将当前线程置于等待条件变量的阻塞状态,并自动释放与之关联的互斥锁。当条件变量被触发时,线程被唤醒并重新获得互斥锁。
  2. 条件触发(单个线程)

    • 函数原型int pthread_cond_signal(pthread_cond_t *cv);
    • 参数说明cv指向条件变量的指针。
    • 操作描述:该函数唤醒在指定条件变量上等待的线程中的一个(如果有的话)。如果没有线程在等待,该函数不执行任何操作。
  3. 条件广播(所有线程)

    • 函数原型int pthread_cond_broadcast(pthread_cond_t *cv);
    • 参数说明cv指向条件变量的指针。
    • 操作描述:该函数唤醒在指定条件变量上等待的所有线程。
7.2.3 注意事项
  1. 互斥锁保护:在修改条件或访问被条件变量保护的共享数据时,必须使用互斥锁来确保数据的一致性和同步性。
  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==============================

结束语

  非常感谢您的耐心阅读!在您即将离开之际,如果您觉得内容有所收获或启发,不妨动动手指,点个赞再走,这将是对我莫大的鼓励和支持。衷心感谢您的点赞与关注,期待未来能继续为您带来更多有价值的内容!谢谢您!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2110005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【在Linux世界中追寻伟大的One Piece】数据链路层

目录 1 -> 数据链路层 2 -> 对比理解“数据链路层”和“网络层” 3 -> 以太网 3.1 -> 以太网的帧格式 4 -> 认识MAC地址 4.1 -> 对比理解MAC地址和IP地址 5 -> 认识MTU 5.1 -> MTU对IP协议的影响 5.2 -> MTU对UDP协议的影响 5.3 -> MT…

如何用涡街流量计计量检测焦炉煤气?

涡街流量计在焦炉煤气计量检测之中仍广泛使用&#xff0c;但也存在一些问题。让我们谈谈这些问题的原因和解决办法。 现场计量检测系统发生故障的原因可归纳为两大原因&#xff0c;一是由流量计或其关联设备引起的。第二种是非流量仪表原因&#xff0c;即流量仪表正常&#xff…

递归求解斐波那契数列的时间复杂度——几种简洁证明

李春葆《算法设计与分析》绿皮--题库版---61页特征方程法&#xff1a;

【原创】java+springboot+mysql企业产品销售管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

动态面板模型

专题名称 第1讲(3 小时) 动态面板模型 面板 VAR 模型 授课内容 一阶差分GMM 估计量(FD-GMM) 序列相关检验、过度识别检验(Sargan 检验) 模型设定常见问题(弱工具变量问题) 面板 VAR 模型简介 允许外生变量的PVAR模型 冲击反应函数(IRF)、方差分解(FEVD) 应用实例(介绍2篇论文) …

无论是速卖通、敦煌网、国际站,自养号测评就是提高曝光的利器!

速卖通&#xff08;AliExpress&#xff09;这是阿里巴巴集团旗下的一个跨境电商平台&#xff0c;主要面向全球零售市场。速卖通店铺根据运营策略不同,可以分为精铺模式、精品模式和垂直模式三种。除了后两种模式需要自有货源外,大部分店铺采用的是“无货源电商”模式。这意味着…

Java栈和队列模拟实现及其方法使用

1. 栈(Stack) 1.1 概念 栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO &#xff08; Last In First Out &#xff09;的原则…

BBR 与 AIMD 共存公平性探究

一个古已有之的结论&#xff1a; deep buffer 场景&#xff0c;bbr 相对 reno/cubic 等 aimd 有优势&#xff0c;侵占性强&#xff1b;shallow buffer 场景&#xff0c;aimd 有优势&#xff0c;bbr 带宽被挤占。 本文用实例分析 why 并给出 how。 先看 deep buffer 场景 bbr…

Rust的数据类型

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 3.5 数据类型的定义和分类 在Rust…

使用html+css+layui实现动态表格组件

1、概述 需求,表格第一列指标可配置通过后端api传进来,表格显示数据以及鼠标触摸后气泡弹出层提示信息都是从后端传过来,实现动态表格的组件!!实现效果如下: 接口标准数据格式如下: {"data": {"date": ["8.20","8.21","…

Cmd终端

组策略停止更新 windows用户的分类 system&#xff08;系统用户&#xff09; administrator&#xff08;管理员用户&#xff09; 普通用户 访客用户 网络管理类命令练习 ping&#xff1a;用于测试网络连接是否正常。通过发送ICMP&#xff08;Internet Control Message Protoco…

力扣 | 递归 | 区间上的动态规划 | 486. 预测赢家

文章目录 一、递归二、区间动态规划 LeetCode&#xff1a;486. 预测赢家 一、递归 注意到本题数据范围为 1 < n < 20 1<n<20 1<n<20&#xff0c;因此可以使用递归枚举选择方式&#xff0c;时间复杂度为 2 20 1024 ∗ 1024 1048576 1.05 1 0 6 2^{20…

Linux--目录与文件操作函数

一、目录和&#xff08;硬&#xff09;链接 可在 shell 中利用 ln 命令为一个业已存在的文件创建新的硬链接 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 2. 同一文件的所有名字&#xff08;链接&#xff09;地位平等—没有一个名字&#xff08;比如…

计应8-01-作业1-静态网页

IP C:\Users\WL>ipconfig // win 查看 ip Windows IP 配置以太网适配器 以太网: //连接特定的 DNS 后缀 . . . . . . . :本地链接 IPv6 地址. . . . . . . . : fe80::6c95:9da6:140c:c59e%13IPv4 地址 . . . . . . . . . . . . : 192.168.51.243//子网掩码 . . . .…

mysql阿拉伯数字转换中文数字函数

函数如下 1.中间一部分代码可以提取出来作为公共方法&#xff0c;我这里并没有提取&#xff0c;因为我是在代码中动态添加的 2.样式目前只做了&#xff1a;123转为一百二十三这类的 drop function if EXISTS zz_convert_number_chinese; create FUNCTION zz_convert_number_…

ELK系列之四---如何通过Filebeat和Logstash优化K8S集群的日志收集和展示

前 言 上一篇文章《日志不再乱: 如何使用Logstash进行高效日志收集与存储》介绍了使用ELK收集通用应用的日志&#xff0c;在目前大多应用都已运行在K8S集群上的环境&#xff0c;需要考虑怎么收集K8S上的日志&#xff0c;本篇就介绍一下如何使用现有的ELK平台收集K8S集群上POD的…

新型供应链攻击手法 — “Revival Hijack”

JFrog 的网络安全研究人员发现了一种名为“Revival Hijack”的新型 PyPI 攻击技术&#xff0c;该技术利用包删除策略绕过安全检查。据统计&#xff0c;超过 22,000 个程序包处于风险之中&#xff0c;可能会影响数十万名用户。 JFrog 的网络安全研究人员发现了一种用于攻击 Pyth…

易灵思时钟输出问题记录

在添加 GPIO时&#xff0c;设置Mode为clkout,并在output Clock中输入时钟名。 这里需要 注意的是&#xff0c; 1. 时钟名不能从core直接输出&#xff0c;而只能使用interface中使用的时钟&#xff0c;如PLL输出的时钟或者GCLK输入的时钟。 2. 易灵思输出时钟不能做其他用途&a…

2024中国产业园区运营商50强榜单揭晓:行业洗牌加速,数智化是关键!

近日&#xff0c;备受瞩目的“2024年度中国产业园区运营商50强”榜单正式揭晓&#xff0c;不仅照亮了行业内的领军之星&#xff0c;更为我们揭示了产业园区运营管理平台在推动经济转型升级中的关键力量与未来趋势的璀璨图景。 从以上产业园区运营商 50 强的角度来看&#xff0…

30岁程序员的焦虑:转行还是继续死磕?现在什么方向更有前景?

最适合转入AI大模型的莫过于程序员和在读大学生了吧。 对于程序员来说&#xff0c;码农之路并不是一帆风顺。对于每一个入行IT业的社会青年来说&#xff0c;谁不是抱着想要成为最高峰的技术大咖或者跃进管理岗的小目标&#xff1f; 然而往往更多的人并非互联网吹捧的如此耀眼…