进程与线程(二)

进程与线程(二)

  • exec函数族
  • 守护进程
    • 守护进程的概念
    • Linux守护进程的编写步骤
      • 创建子进程,父进程退出
      • 在子进程中创建新会话
      • 改变当前目录为根目录
      • 重设文件权限掩码
      • 关闭文件描述符
        • 守护进程案例编写
  • 线程
    • 线程的概念
    • Linux下进程和线程的区别
  • Linux下线程的相关函数
    • 创建线程
    • 线程等待(阻塞)
    • 线程分离(非阻塞)
      • 线程的自动退出
      • 线程的取消(被动退出)
  • 线程间的通信
    • 测试全局变量和堆区数据共享
    • 主线程给子线程传值
    • 子线程给主线程传值
  • 线程中易出现的问题
    • 互斥
    • 互斥锁
      • 互斥锁的初始化
      • 上锁
      • 解锁
      • 销毁互斥锁
    • 同步
      • 同步的技术(基于信号量)
      • 信号量的初始化
      • 信号量的P操作
      • 信号量的V操作

exec函数族

exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

何时使用?
当进程认为自己不能再为系统和用户做出任何贡献了时就可以调用exec函数,让自己执行新的程序
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例:练习使用exec函数族

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
	//功能:模拟一个shell终端
	while(1)
	{
		char command[20] = {0};
		printf("linux@ubuntu:~$");
		scanf("%s",command);
		pid_t pid = fork();
		if(pid < 0)
		{
			perror("fork error");
			return -1;
		}
		else if(0 == pid)
		{
			//子进程
			char *argv[] = {command, "-la", NULL};//这里的-la不加,则演示不出来ls -la的效果,因为输入的ls -la会被直接赋值给command,而command又是可执行文件的名字,此时被识别的是该文件不存在! 方法:想办法将输入的ls -la中的-la能够单独保存起来,并且存储
			在子进程中的argv[]指针数组中即可。
			int result = execvp(command, argv);
			if(errno == ENOENT)
			{
				printf("文件不存在!\n");
			}
			else if(errno == EFAULT)
			{
				printf("请以NULL 结尾!\n");
			}
			else if(errno == EACCES)
			{
				printf("可执行文件没有执行权限\n");
			}
		}
		else
		{
			//父进程
			wait(NULL);
		}
	}
	return 0;
}

守护进程

守护进程的概念

守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件
守护进程常常在系统启动时开始运行,在系统关闭时终止
Linux系统有很多守护进程,大多数服务都是用守护进程实现的

在Linux中,每一个系统与用户进行交流的界面称为终端。从该终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。
守护进程能够突破这种限制,它从开始运行,直到整个系统关闭才会退出。如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进程变成一个守护进程

Linux守护进程的编写步骤

创建子进程,父进程退出

//第一步完成以后,子进程就在形式上做到了与控制终端的脱离
//由于父进程已经先于子进程退出,子进程变成孤儿进程
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
pid = fork();
if (pid > 0) /*父进程退出*/
{
	exit(0);
}

在子进程中创建新会话

进程组:
进程组是一个或多个进程的集合。进程组由进程组ID来唯一标识。
每个进程组都有一个组长进程,进程组ID就是组长进程的进程号。
会话期:
会话组是一个或多个进程组的集合

在这里插入图片描述

#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
setsid函数作用:
setsid函数用于创建一个新的会话,并使得当前进程成为新会话组的组长
setsid函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

改变当前目录为根目录

通常的做法是让“/”或”/tmp”作为守护进程的当前工作目录 。
在进程运行过程中,当前目录所在的文件系统是不能卸载的。
chdir函数可以改变进程当前工作目录
#include <unistd.h>
int chdir(const char *path);

重设文件权限掩码

文件权限掩码是指文件权限中被屏蔽掉的对应位。把文件权限掩码设置为0,可以增加该守护进程的灵活
性。
设置文件权限掩码的函数是umask
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
通常的使用方法为umask(0)

关闭文件描述符

新建的子进程会从父进程那里继承所有已经打开的文件。
获取继承过来的所有文件描述符的方法:
#include <unistd.h>
int getdtablesize(void);
在创建完新的会话后,守护进程已经脱离任何控制终端,应当关闭用不到的文件
fdtablesize = getdtablesize();
for (fd = 0; fd < fdtablesize; fd++)
{
close(fd);
}

注意:创建守护进程的步骤中,前两步已经实现了守护进程的创建,后面的三步都是优化工作!!!

守护进程案例编写

守护进程功能:每隔1秒,将获取的系统时间写入到指定的文件例如:/time.log中去**
PS:运行守护进程时,将编译好的可执行文件拷贝到守护进程指定的目录下,使用sudo来运行可执行文件。

在这里插入图片描述
在这里插入图片描述

线程

在这里插入图片描述

线程的概念

在操作系统中引入进程的目的,是为了使多个程序能并发执行,以提高资源利用率和系统吞吐量,那么,在操作系统中再引入线程,则是为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性。
为了说明这一点,我们首先来回顾进程的两个基本属性:
1、进程是一个可拥有资源的独立单位;
2、进程同时又是一个可独立调度和分派的基本单位。正是由于进程有这两个基本属性,才使之成为一个能独立运行的基本单位,从而也就构成了进程并发执行的基础。然而,为使程序能并发执行,系统还必须进行以下
的一系列操作:
(1)创建进程:系统在创建一个进程时,必须为它分配其所必需的、除处理机以外的所有资源,如内存空间、I/O 设备,以及建立相应的PCB。
(2)撤消进程:系统在撤消进程时,又必须先对其所占有的资源执行回收操作,然后再撤消PCB。
(3)进程切换:对进程进行切换时,由于要保留当前进程的CPU环境和设置新选中进程的CPU环境,因而须花费不少的处理机时间。
换言之,由于进程是一个资源的拥有者,因而在创建、撤消和切换中,系统必须为之付出较大的时空开销。正因如此,在系统中所设置的进程,其数目不宜过多,进程切换的 频率也不宜过高,这也就限制了并发程度的进一步提高。
故:如何能使多个程序更好地并发执行同时又尽量减少系统的开销,已成为近年来设计操 作系统时所追求的重要目标。有不少研究操作系统的学者们想到,若能将进程的上述两个属性分开,由操作系统分开处理,即对于作为调度和分派的基本单位,不同时作为拥有资源的单位,以做到“轻装上阵”;而对于拥有资源的基本单位,又不对之进行频繁的切换。
正是在这种思想的指导下,形成了线程的概念。

Linux下进程和线程的区别

线程具有许多传统进程所具有的特征,所以又称为轻型进程(Light-Weight Process)或进程元,相应地把传统进程称为重型进程(Heavy-Weight Process),传统进程相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都拥有若干个线程,至少也有一个线程。下面我们从调度性、并发性、系统开销和拥有资源等方面对线程和进程进行比较。

(1)调度:线程为调度和分派的基本单位,进程为拥有资源的基本单位。
在传统的操作系统中,作为拥有资源的基本单位和独立调度、分派的基本单位都是进程。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位,而进程作为资源拥有的基本单位,把传统进程的两个属性分开,使线程基本上不拥有资源,这样线程便能轻装前进,从而可显著地提高系统的并发程度。在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。
(2)并发性:不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行。
这样使得操作系统具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量。
例如,在一个未引入线程的单CPU操作系统中,若仅设置一个文件服务进程,当该进程由于某种原因而被阻塞时,便没有其它的文件服务进程来提供服务。在引入线程的操作系统中,则可以在一个文件服务进程中设置多个服务线程。当第一个线程等待时,文件服务进程中的第二个线程可以继续运行,以提供文件服务;当第二个线程阻塞时,则可由第三个继续执行,提供服务。显然,这样的方法可以显著地提高文件 服务的质量和系统的吞吐量。
(3)拥有资源:线程拥只拥有一点必不可少的资源(例如栈)–》轻量级的进程,但可访问隶属进程的资源。
不论是传统的操作系统,还是引入了线程的操作系统,进程都可以拥有资源,是系统中拥有资源的一个基本单位。一般而言,线程自己不拥有系统资源(也有一点必不可少的资源),但它可以访问其隶属进程的资源,即一个进程的代码段、数据段及所拥有的系统资源, 如已打开的文件、I/O设备等,可以供该进程中的所有线程所共享。
(4)系统开销:控制进程明显大于控制线程的开销。
在创建或撤消进程时,系统都要为之创建和回收进程控制块(PCB),分配或回收资源。
如内存空间和I/O设备等,操作系统所付出的开销明显大于线程创建或撤消时的开销。类似地,在进程切换时,涉及到当前进程CPU环境的保存及新被调度运行进程的CPU环境的设置, 而线程的切换则仅需保存和设置少量寄存器内容,不涉及存储器管理方面的操作,所以就切换代价而言,进程也是远高于线程的。此外,由于一个进程中的多个线程具有相同的地址空间,在同步和通信的实现方面线程也比进程容易。在一些操作系统中,线程的切换、 同步和通信都无须操作系统内核的干预。

总结:
1、线程的特点

1、共享进程的地址空间
2、对于操作系统而言,进程和线程没有区别,都使用task_struct来描述,参与统一的调度。
3、线程是系统调度的最小单位
4、通过第三方库来实现,编译时需要添加:-lpthread

2、线程的优缺点

线程的特征和进程差不多,进程有的他基本都有,比如:
线程具有就绪、阻塞、运行三种基本状态,同样具有状态之间的转换关系;
线程间可以并发执行 在多 CPU 环境下,各个线程也可以分派到不同的 CPU 上并行执行
线程的优点:
一个进程中可以同时存在多个线程,这些线程共享该进程的资源。进程间的通信必须请求操作系统服务 (因为 CPU 要切换到内核态),开销很大。而同进程下的线程间通信,无需操作系统干预,开销更小。不过,需要注意的是:从属于不同进程的线程间通信,也必须请求操作系统服务。 线程间的并发比进程的开销更小,系统并发性提升。同样,需要注意的是:从属于不同进程的线程间切换,它是会导致进程切换的,所以开销也大。
线程的缺点: 当进程中的一个线程奔溃时,会导致其所属进程的所有线程奔溃

Linux下线程的相关函数

创建线程

在这里插入图片描述

案例:创建子线程1播放音乐,创建子线程2播放视频

在这里插入图片描述
优化:引入线程等待函数:pthread_join()函数

线程等待(阻塞)

在这里插入图片描述
上述代码优化之后,如下:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程1
void * playMusic(void *arg)
{
	while(1)
	{
		printf("playMusic......\n");
		sleep(1);
	}
}
//线程2
void * playVedio(void *arg)
{
	while(1)
	{
		printf("playVedio......\n");
		sleep(1);
	}
}
int main(int argc, const char *argv[])
{
	//创建两个子线程一个播放音乐 ,一个播放视频
	pthread_t thID1, thID2;
	pthread_create(&thID1, NULL, &playMusic, NULL);
	pthread_create(&thID2, NULL, &playVedio, NULL);
	//线程等待函数:主线程此时以阻塞的方式等待回收子线程的退出资源
	pthread_join(thID1, NULL);
	pthread_join(thID2, NULL);
	//此时主线程根本不会执行到这里,因为pthread_join()是一个阻塞等待函数,子线程不结
	束,主线程是不会往下继续执行的
	while(1)
	{
		printf("do working......\n");
		sleep(1);
	}
	return 0;
}

运行结果如下:
在这里插入图片描述

线程分离(非阻塞)

线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他进程获取,而直接自己自动释放。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生是由于进程死后,大部分资源被释放,一点残余(task_struct结构)资源仍存在于系统中,导致内核认为该进程仍然存在。

函数原型:int pthread_detach(pthread_t thread);
功能:实现线程分离
函数返回值:
成功返回0;
失败返回失败号;
注意:不能对一个已经处于detach状态的线程调用pthread_join函数,这样的调用将返回EINVAL错误

上述代码优化为:
在这里插入图片描述
注意:当使用pthread_detach线程分离函数时,如果主线程此时没有任何事情干,而选择return 0之后,子线程是不会被执行到的,因此主线程要么使用pthread_join阻塞等待或者使用了pthread_detach之后干自己的事情,主线程不要退出即可!

线程的自动退出

在这里插入图片描述

线程的取消(被动退出)

在这里插入图片描述
案例:结合使用pthread_exit()和pthread_cancel()

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
//线程1
void * playMusic(void *arg)
{
	time_t start_time,current_time;
	time(&start_time);//获取起始系统时间
	while(1)
	{
		time(&current_time);//获取当前系统时间
		//5秒之后,则让子线程1主动退出
		if(current_time - start_time > 5)
		{
			//子线程1自己主动退出
			printf("线程1 已退出!\n");
			pthread_exit(NULL);
		}
		printf("playMusic......\n");
		sleep(1);
	}
}
//线程2
void * playVedio(void *arg)
{
	while(1)
	{
		printf("playVedio......\n");
		sleep(1);
	}
}
int main(int argc, const char *argv[])
{
	//创建两个子线程一个播放音乐 ,一个播放视频
	pthread_t thID1, thID2;
	pthread_create(&thID1, NULL, &playMusic, NULL);
	pthread_create(&thID2, NULL, &playVedio, NULL);
	//线程分离:子线程与主线程脱离了关系,子线程退出时,自己直接自动释放
	pthread_detach(thID1);
	pthread_detach(thID2);
	time_t start_time, current_time;
	time(&start_time);
	while(1)
	{
		//获取当前时间和起始时间的时间戳,大于10秒则取消线程2
		time(&current_time);
		if(current_time - start_time > 10)
		{
			//取消线程2(线程2被被动的取消)
			pthread_cancel(thID2);
			printf("已成功取消线程2!\n");
			pthread_exit(NULL);//主线程自己退出
		}
		//当时间小于10秒时,主线程还是工作
		printf("do working...\n");
		sleep(1);
	}
	return 0;
}

在这里插入图片描述

线程间的通信

测试全局变量和堆区数据共享

全局变量共享:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
//定义全局变量a = 5;
int a = 5;
//线程1:对于a的值加2进行输出
void * thread1_func(void *arg)
{
	a += 2;
	printf("thread1_func:a = %d\n",a);
}
//线程2:对于a的值加5进行输出
void * thread2_func(void *arg)
{
	a += 5;
	printf("thread2_func:a = %d\n",a);
}
int main(int argc, const char *argv[])
{
	//main函数一进来,则直接打印全局变量a的结果
	printf("main_func:a = %d\n",a);
	pthread_t thID1, thID2;
	pthread_create(&thID1, NULL, &thread1_func, NULL);
	pthread_create(&thID2, NULL, &thread2_func, NULL);
	//主线程阻塞等待子线程退出
	pthread_join(thID1, NULL);
	pthread_join(thID2, NULL);
	return 0;
}

在这里插入图片描述
堆区数据共享:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
//定义指向堆区空间的指针
char *p = NULL;
//线程1:给p指向的空间连接thread1_func
void * thread1_func(void *arg)
{
	strcat(p, "thread1_func");
	printf("%s\n",p);
}
//线程2:给p指向的空间连接thread2_func
void * thread2_func(void *arg)
{
	strcat(p, "thread2_func");
	printf("%s\n",p);
}
int main(int argc, const char *argv[])
{
	//main函数一进来,给全局变量p在堆区申请一片100个字节的空间
	p = (char *)malloc(100);
	if(NULL == p)
	{
		printf("malloc error!\n");
		return -1;
	}
	printf("malloc ok!\n");
	//清空
	bzero(p, 100);
	//连接一个字符串
	strcat(p, "main_func");
	//打印p指向空间的内容
	printf("%s\n",p);
	pthread_t thID1, thID2;
	pthread_create(&thID1, NULL, &thread1_func, NULL);
	pthread_create(&thID2, NULL, &thread2_func, NULL);
	//主线程阻塞等待子线程退出
	pthread_join(thID1, NULL);
	pthread_join(thID2, NULL);
	return 0;
}

运行结果如下:
在这里插入图片描述

主线程给子线程传值

主线程给子线程传值:abcdefg
主线程给子线程传值:12

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
//线程1:主线程给子线程1传值abcdefg
void * thread1_func(void *arg)
{
	char *src = (char *)arg;
	printf("给子线程1传入的数值为:%s\n",src);
}
//线程2:主线程给子线程2传值12
void * thread2_func(void *arg)
{
	int *src = (int *)arg;
	printf("给子线程2传入的数值为:%d\n",*src);
}
int main(int argc, const char *argv[])
{
	pthread_t thID1, thID2;
	char str[] = {"abcdefg"};
	pthread_create(&thID1, NULL, &thread1_func, str);
	int num = 12;
	pthread_create(&thID2, NULL, &thread2_func, &num);
	//主线程阻塞等待子线程退出
	pthread_join(thID1, NULL);
	pthread_join(thID2, NULL);
	return 0;
}

运行结果:
在这里插入图片描述

子线程给主线程传值

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
//线程1:主线程给子线程1传值abcdefg
void * thread1_func(void *arg)
{
	char *src = (char *)arg;
	printf("给子线程1传入的数值为:%s\n",src);
	//将子线程1接收过来的参数赋值给动态申请的内存空间
	char *p = (char *)malloc(100);
	strcpy(p, src);
	strcat(p, ".PNG");
	//子线程1退出时,将p的地址进行返回给主线程
	pthread_exit(p);
}
//线程2:主线程给子线程2传值12
void * thread2_func(void *arg)
{
	int *src = (int *)arg;
	printf("给子线程2传入的数值为:%d\n",*src);
}
int main(int argc, const char *argv[])
{
	pthread_t thID1, thID2;
	char str[] = {"abcdefg"};
	pthread_create(&thID1, NULL, &thread1_func, str);
	int num = 12;
	pthread_create(&thID2, NULL, &thread2_func, &num);
	//主线程阻塞等待子线程退出
	char *s = NULL;
	pthread_join(thID1, (void **)&s);//通过形参让s得到空间首地址
	//打印
	printf("子线程1给主线程返回的数值:s = %s\n",s);
	//当子线程1退出时,
	pthread_join(thID2, NULL);
	return 0;
}

运行结果:
在这里插入图片描述

线程中易出现的问题

互斥

案例:购票问题
1、窗口买票
2、App买票
假设购买的车次信息为:G2073 05车13A(西安北站-宝鸡南站)时间51分钟 ,此时购买的过程中其他人是不能再访问该车次的!
因为要保证临界资源的完整性。

案例:

定义一个全局的buf–》char buf[20] = {‘\0’};//临界资源
主线程给子线程1发送“0123456789”,子线程将这些值一个一个复制给buf
主线程给子线程1发送“ABCDEFGHIJ”,子线程将这些值一个一个复制给buf
主线程给子线程1发送“abcdefghij”,子线程将这些值一个一个复制给buf

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <strings.h>
//定义全局变量
char Message[20] = {0};//临界资源
//子线程thread_func
void * thread_func(void *arg)
{
	//接收子进程传入进来的参数
	char *string = (char *)arg;
	//清空
	bzero(Message, sizeof(Message));
	int i = 0;
	while(*string)
	{
		Message[i++] = *string++;
		printf("%s\n", Message);
		usleep(300000);//0.3秒
	}
	Message[i] = '\0';
	printf("Message = %s\n",Message);//打印Message的拷贝之后完整信息
}
int main(int argc, const char *argv[])
{
	//创建三个子线程
	pthread_t pid1, pid2, pid3;
	char buf1[20] = {"0123456789"};
	char buf2[20] = {"ABCDEFGHIJ"};
	char buf3[20] = {"abcdefghij"};
	//创建3个子线程,并给3个子线程的处理函数传入3个参数
	pthread_create(&pid1, NULL, thread_func, buf1);
	pthread_create(&pid2, NULL, thread_func, buf2);
	pthread_create(&pid3, NULL, thread_func, buf3);
	//主线程阻塞等待子线程结束,并回收其退出资源
	pthread_join(pid1, NULL);
	pthread_join(pid2, NULL);
	pthread_join(pid3, NULL);
	/*pthread_detach(pid1);
	pthread_detach(pid2);
	pthread_detach(pid3);*/
	//while(1);//因为使用了detach函数,因此不想要让主线程退出导致
	//子线程不会被执行到,加了一个while(1);
	return 0;
}

运行结果:
在这里插入图片描述
分析:出现以上结果的原因
此时三个子线程在同时操作Message这个临界资源,因此出现的结果就是数据比较混乱!
我们想要的效果是,三个子线程再操作临时资源Message时是互不影响的(互斥)!

互斥锁

引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。
互斥锁主要用来保护临界资源
每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源
线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止

互斥锁的初始化

在这里插入图片描述

上锁

在这里插入图片描述

解锁

在这里插入图片描述

销毁互斥锁

在这里插入图片描述
案例:引入互斥锁来让多个子线程在操作临界资源时,保持临界资源的完整性

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <strings.h>
//定义一个临界资源,可以满足被3个子线程同时访问
char Message[20] = {0};
//定义一把互斥锁
pthread_mutex_t MyMutex;
//线程处理函数
void * thread_func1(void *arg)
{
	//传入的是buf1-->复制到Message中
	char *buf1 = (char *)arg;
	//上锁
	pthread_mutex_lock(&MyMutex);
	//清空空间为'\0'
	bzero(Message, sizeof(Message));
	//拷贝字符串
	int i = 0;
	while(*buf1)
	{
		Message[i++] = *buf1++;
		printf("%s\n",Message);
		usleep(300000);
	}
	printf("Message = %s\n",Message);
	//解锁
	pthread_mutex_unlock(&MyMutex);
}
void * thread_func2(void *arg)
{
	//传入的是buf1-->复制到Message中
	char *buf2 = (char *)arg;
	//上锁
	pthread_mutex_lock(&MyMutex);
	//清空空间为'\0'
	bzero(Message, sizeof(Message));
	//拷贝字符串
	int i = 0;
	while(*buf2)
	{
		Message[i++] = *buf2++;
		printf("%s\n",Message);
		usleep(300000);
	}
	printf("Message = %s\n",Message);
	//解锁
	pthread_mutex_unlock(&MyMutex);
}
void * thread_func3(void *arg)
{
	//传入的是buf3-->复制到Message中
	char *buf3 = (char *)arg;
	//上锁
	pthread_mutex_lock(&MyMutex);
	//清空空间为'\0'
	bzero(Message, sizeof(Message));
	//拷贝字符串
	int i = 0;
	while(*buf3)
	{
		Message[i++] = *buf3++;
		printf("%s\n",Message);
		usleep(300000);
	}
	printf("Message = %s\n",Message);
	//解锁
	pthread_mutex_unlock(&MyMutex);
}
int main(int argc, const char *argv[])
{
	//初始化互斥锁
	pthread_mutex_init(&MyMutex, NULL);
	//创建三个子线程,主线程分别给三个子线程传参
	//子线程的功能:把传入的buf中的内容拷贝到临界资源中,最后输出临界资源值
	char buf1[20] = {"0123456789"};
	char buf2[20] = {"ABCDEFGHIJ"};
	char buf3[20] = {"abcdefghij"};
	pthread_t thID1,thID2,thID3;//线程ID
	pthread_create(&thID1, NULL, thread_func1, buf1);
	pthread_create(&thID2, NULL, thread_func2, buf2);
	pthread_create(&thID3, NULL, thread_func3, buf3);
	//线程等待(阻塞)且回收资源
	pthread_join(thID1, NULL);
	pthread_join(thID2, NULL);
	pthread_join(thID3, NULL);
	//主线程负责销毁互斥锁
	pthread_mutex_destroy(&MyMutex);
	return 0;
}

运行结果:
在这里插入图片描述

同步

同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
1968年,Edsgar Dijkstra基于信号量的概念,提出了一种同步机制
由信号量来决定线程是继续运行还是阻塞等待

例如:定义全局Message
编写一子线程给Message输入
编写一子线程将Message中的数据输出
如何实现–》需要同步!!!(先使用子线程1完成对Message空间的输入,在使用子线程2将输入的信
息打印出来)

同步的技术(基于信号量)

信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问
1、初始化
2、P操作(申请资源)
3、V操作(释放资源)
信号量的值为非负整数,它被用来控制对公共资源的访问.

PV操作的含义是

P含义如下:
if (信号量的值大于0)
{
申请资源的任务继续运行;
信号量的值减一;
}
else
{
申请资源的任务阻塞;
}
V含义如下:
if (没有任务在等待该资源)
{
信号量的值加一;
}
else
{
唤醒第一个等待的任务,让其继续运行
}

信号量的初始化

在这里插入图片描述

信号量的P操作

在这里插入图片描述

信号量的V操作

在这里插入图片描述
案例:子进程1先实现输入,子线程2再实现输出!
在这里插入图片描述

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
char Data[20] = {0};//临界资源
//定义信号量
sem_t sem1,sem2;
void *fun1(void *arg)
{
	while(1)
	{
		//P操作(申请资源)
		sem_wait(&sem1);
		//输入
		printf("请输入:");
		fgets(Data, sizeof(Data), stdin);
		//V操作(释放资源)
		sem_post(&sem2);
	}
}
void *fun2(void *arg)
{
	while(1)
	{
		//P操作(申请资源)
		sem_wait(&sem2);
		//输出
		printf("输出为:");
		fputs(Data, stdout);
		//V操作(释放资源)
		sem_post(&sem1);
	}
}
int main(int argc, const char *argv[])
{
	//信号量初始化
	sem_init(&sem1, 0, 1);//sem1=1
	sem_init(&sem2, 0, 0);//sem2=0
	pthread_t thID1, thID2;
	pthread_create(&thID1, NULL, fun1, NULL);
	pthread_create(&thID2, NULL, fun2, NULL);
	pthread_join(thID1, NULL);
	pthread_join(thID2, NULL);
	//信号量的销毁
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	return 0;
}

同步机制运行结果:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++基础编程100题-002 OpenJudge-1.1-04 输出保留3位小数的浮点数

更多资源请关注纽扣编程微信公众号 002 OpenJudge-1.1-04 输出保留3位小数的浮点数 http://noi.openjudge.cn/ch0101/04/ 描述 读入一个单精度浮点数&#xff0c;保留3位小数输出这个浮点数。 输入 只有一行&#xff0c;一个单精度浮点数。 输出 也只有一行&#xff0c;…

java —— 集合

一、集合的概念 集合可以看做是一个存储对象的容器&#xff0c;与数组不同的是集合可以存储不同类型的对象&#xff0c;但开发中一般不这样做。集合不能存储基本类型的对象&#xff0c;如果存储则需要将其转化为对应的包装类。 二、集合的分类 集合分为 Collection 和 Map 两…

2024抖音流量认知课:掌握流量底层逻辑,明白应该选择什么赛道 (43节课)

课程下载&#xff1a;https://download.csdn.net/download/m0_66047725/89360865 更多资源下载&#xff1a;关注我。 课程目录 01序言&#xff1a;拍前请看.mp4 02抖音建模逻辑1.mp4 03抖音标签逻辑2.mp4 04抖音推流逻辑3.mp4 05抖音起号逻辑4.mp4 06养号的意义.mp4 0…

算法解析——单身狗问题

欢迎来到博主的专栏&#xff1a;算法解析 博主ID代码小豪 文章目录 什么是单身狗问题leetcode_136——只出现一次的数字I使用位运算解决单身狗问题。 leetcode_137——只出现一次的数字II统计二进制数解决单身狗问题leetcode_260 只出现一次数字III分区域按位异或解决问题。 总…

iperf3带宽压测工具使用

iperf3带宽压测工具使用 安装下载地址&#xff1a;[下载入口](https://iperf.fr/iperf-download.php)测试结果&#xff1a;时长测试&#xff08;压测使用&#xff09;:并行测试反向测试UDP 带宽测试 iPerf3 是用于主动测试 IP 网络上最大可用带宽的工具 安装 下载地址&#x…

SAP 生产订单批量报工(代码分享)

最近公司一直在对成本这块的业务进行梳理,影响比较大的就是生产这块的报工,经常会要求要批量的冲销报工,然后在继续报工,来调整生产订单的实际工时,前面的博客中已经给大家分享了批量冲销生产订单的代码, 下面给大家分享一下生产订单批量报工的代码 首先流程制造和离散制…

如何解决研发数据传输层面安全可控、可追溯的共性需求?

研发数据在企业内部跨网文件交换&#xff0c;是相对较为普遍而频繁的文件流转需求&#xff0c;基于国家法律法规要求及自身安全管理需要&#xff0c;许多企业进行内部网络隔离。不同企业隔离方案各不相同&#xff0c;比如银行内部将网络隔离为生产网、办公网、DMZ区&#xff0c…

如何用ChatGPT上热门:完整使用教程与写作技巧

1. ChatGPT概述修订 ChatGPT是一款基于深度神经网络的语言生成技术&#xff0c;能够协助用户创造出各类高品质的文字材料&#xff0c;适宜广泛的应用场景&#xff0c;如编撰文章、文学创作及社交媒体内容生成。 2. 利用ChatGPT生成热门内容的基本步骤 为了有效利用ChatGPT创作…

代码随想录算法训练营第三十五 | ● 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球

860.柠檬水找零 讲解链接&#xff1a;https://programmercarl.com/0860.%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.html 本题只有5元10元20元&#xff0c;只需要考虑收到5、10、20这三种情况&#xff1b; 收到5元&#xff0c;五块的个数&#xff1b; 收到10&#xff0c;找…

Appium安装及配置(Windows环境)

在做app相关自动化测试&#xff0c;需要使用appium来做中转操作&#xff0c;下面来介绍一下appium的环境安装配置 appium官方文档&#xff1a;欢迎 - Appium Documentation 一、下载appium 下载地址&#xff1a;https://github.com/appium/appium-desktop/releases?page3 通…

Java多线程--volatile关键字

并发编程的三大特性 可见性有序性原子性 可见性 为什么会有可见性问题&#xff1f; 多核CPU 为了提升CPU效率&#xff0c;设计了L1&#xff0c;L2&#xff0c;L3三级缓存&#xff0c;如图。 如果俩个核几乎同时操作同一块内存&#xff0c;cpu1修改完&#xff0c;当下是对c…

TDR原理的介绍

目录 简介 简单定义 TDR测试原理 简介 时域和频域就像孪生兄弟一样&#xff0c;经常在测试测量领域同时出现&#xff0c;可谓是工程师们分析问题和解决问题的两大法宝。所以&#xff0c;在某些测试场景中&#xff0c;如果有时域信息的护法&#xff0c;咱们就能从时频域两个维…

嵌入式Linux shell编程实例

1. 输入两个数&#xff0c;实现两个数的相加 &#xff08;1&#xff09;具体实现代码如下 1 #!/bin/bash2 read a3 read b4 sum$(($a$b))5 echo "$sum"&#xff08;2&#xff09;编辑完内容后按Esc键再输入:wq保存&#xff0c;回车退出&#xff0c;执行结果如下图&a…

uniapp创建支付密码实现(初始密码,第二次密码)

示例&#xff1a; 插件地址&#xff1a;自定义数字/身份证/密码输入框&#xff0c;键盘密码框可分离使 - DCloud 插件市场 1.下载插件并导入HBuilderX&#xff0c;找到文件夹&#xff0c;copy number-keyboard.vue一份为number-keyboard2.vue&#xff08;number-keyboard.vue是…

[数据集][目标检测]脑溢血检测数据集VOC+YOLO格式767张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;767 标注数量(xml文件个数)&#xff1a;767 标注数量(txt文件个数)&#xff1a;767 标注类别…

java 远程调试

1.远程启动时 jdk1.8-32\jre\bin\java.exe -Dfile.encodingUTF-8 -Djava.library.pathlib -agentlib:jdwptransportdt_socket,servery,suspendn,address5005 -jar local-com.yuetai.service-0.0.1-SNAPSHOT.jar --spring.config.locationapplication.yml 2.本地调试项目连接远…

关于前端代码移动端的适配方案

为什么需要适配&#xff1f; 由于PC端和移动端的分辨率不同&#xff0c;前端展示的页面在两端设备如果原模原样的搬运则会导致PC端或移动端看到的画面相对于其设备的分辨率及其的不合理。 最为常见的是PC端正常浏览的网页没有做移动端适配&#xff0c;由于移动端分辨率普遍低于…

Base64码转换

title: Base64码转换 date: 2024-06-01 20:30:28 tags: vue3 后端图片前端显示乱码 现象 后端传来一个图片&#xff0c;前端能够接收&#xff0c;但是console.log()后发现图片变成了乱码&#xff0c;但是检查后台又发现能够正常的收到了这张图片。 处理方法 笔者有尝试将图…

虚拟现实环境下的远程教育和智能评估系统(三)

本周继续进行开发工具的选择与学习&#xff0c;基本了解了以下技术栈的部署应用&#xff1b; 一、Seata&#xff1a; Seata&#xff08;Simple Extensible Autonomous Transaction Architecture&#xff09;是一款开源的分布式事务解决方案&#xff0c;旨在提供高性能和简单易…

WordPress plugin MStore API SQL注入漏洞复现(CVE-2023-3077)

0x01 产品简介 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 0x02 漏洞概述 WordPress plugin MStore API 3.9.8 版本之前存在S…