进程与线程(二)
- 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(¤t_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(¤t_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;
}
同步机制运行结果: