目录
1.wait函数介绍
- wststus参数,输出类型的参数,进程的状态改变的原因的相关信息,如果我们不关心这个子进程为什么状态发生了改变,我们就可以把这个参数设置为nullptr;
- 终止的子进程的pid号码作为这个wait函数的返回值;
- wait等待任意的一个子进程终止退出,如果子进程都不结束,wait将会一直处于一个阻塞的状态,有一个子进程终止,这个函数就会有对应的终止进程的返回值;
- 所有子进程全部终止结束,这个时候的wait函数的返回值就是-1;
2.wait函数演示
创建3个子进程,分别是5,10,15s之后结束,查看这个父进程等待的情况以及wait函数的返回值的情况;
下面将会通过代码实现这个过程:我们创建一个已知的进程,我们可以称之为父进程,这个父进程创建了三个子进程,分别给这三个子进程创建休眠的时间,5s,10s,15s等等;
这个时候的main函数里面设置了相关的参数,其中这个里面的第一个参数就是argc,这个参数的意义就是我们的main函数里面的参数的个数,第二个argv实际上就是我们的参数序列或者是指针,可以下去自行了解;
我们的这个for循环里面主要就是创建子进程(fork函数),打印这个子进程的id(getpid函数)和休眠时间,因为这个终端输出的是字符10,5,15,因此我们设置的休眠时间的时候需要使用这个atoi函数把这个字符转换为int类型的数据;
因为是3个子进程,但是这个下标是0,1,2,3,其中这个里面的0表示的就是我们的父进程,1,2,3分别表示的就是3个子进程,因此我们的这个for循环是从这个1开始循环的,当返回值是-1的时候,就说明这三个子进程全部结束了;
其中这个里面的while循环会一直进行,返回值-1这个子进程全部结束,我们打印输出一句话进行说明,同时记录下来这个子进程结束的个数,当返回值-1的时候在,合格循环就执行exit异常退出
运行输出结果:以及我们的输入的相关的要求,我们需要输出我们的这个子进程运行的时间,这个里面就是./a.out就是我们编译之后生成的可执行程序,10,5,15就是子进程的休眠时间,这个就是对应的我们的这个main函数里面的参数;
3.pthread_create函数介绍
3.1总体介绍
这个函数就是线程的创建函数,线程是有进程创建出来的,线程的资源都来自于创建线程的进程;
我们使用gcc进行编译和连接的时候需要使用-pthread选项,告诉编译器这个函数在哪个位置,这个compile和link就是编译和连接的意思;
3.2参数介绍
这个里面的第三个参数的意义就是我们的函数指针,这个指针的指向的地址存储的就是我们的这个线程需要执行的内容,这个函数指针的返回值就是void*类型的,函数指针的参数就是void*类型的
第四个参数是传递给线程的参数,如果我们没有给这个线程传递任何的参数,我们直接把第四个参数设置为空指针就可以了;
第一个参数是一个输出类型的参数,运行完这个线程之后会把这个线程的id号放到这个参数里面去,不需要我们人为设置;
第二个参数attr是一个结构体类型的指针,决定我们创建新的线程的属性,这个线程的属性,我们就放到这个结构体的属性,我们使用默认属性的话直接使用null这个默认的属性即可;
3.3返回值说明
正常的返回值就是0,如果返回值是一个非0数值,就说明这个函数执行过程出现错误;
3.4进程线程关系演示
进程结束,操作系统就会回收所有的资源和空间,线程依赖于进程,这个时候线程就不可以继续运行了;
下面的这个代码就是对于这个用法进行了说明,我们调用这个pthread_create函数创建一个新的线程,这个函数的参数就是我们上面介绍的,其中这个里面的第一个参数就是一个输出型的参数,只需要把我们自己定义的这个线程的地址传递过去就可以了;
第三个参数是一个函数指针,这个函数指针就是我们的函数名,因为在函数指针里面,函数名就是地址,第二个参数就是线程的属性,这个我们如果不需要的话就写作空指针即可,第四个参数是传递给线程的参数,我们后面会讲到(第五个部分会使用到),现在我们只用一下这个第一个和第三个参数,如果返回值是0,说明是正常的,不是0的话,我们就需要使用这个perror打印输出错误的原因;
我们的打印结果应该是10次的hello world,但是因为这个进程创建的线程,我们的进程里面的while会让这个线程一直打印,打印10次之后,这个线程就会退出,但是这个进程不会退出,这个进程就无法知道这个线程什么时候结束的;
但是如果我们不写这个while(1)因为这个进程就会直接return 0了,这个时候进程系统资源被回收,我们的这个线程就是进程创建的,因此这个时候的线程就不会输出这个打印的结果;
下面我们会介绍这个pthread_join函数解决这个问题:
4.pthread_join函数
这个函数第一个参数就是我们的的线程的名字,第二个参数就是一个指针变量,如果这个线程结束了之后,这个时候的pthread_join函数就会返回0,相当于告诉我们的进程,我们的线程已经结束了
第一个参数表示的就是我们等待哪一个进程结束,第二个参数保存的是线程的退出状态,不需要等待就是null指针;下面的这个代码里面,就是解决上面的那个while(1)的遗留问题的;
5.pthread_create函数第四个参数
第四个参数就是我们的进程传递给线程的参数,我们应该如何使用呢;例如还是这个循环打印10次hello world的语句,我们可以让这个第四个参数是ret=10的地址,这样的话这个10就可以传递给我们的函数指针的参数了,也就是我们的arg参数,这个时候的arg参数就是10,我们如何使用这个arg控制我们的循环呢?
这个时候就是首先要进行这个指针的解引用,但是因为这个原来的参数agr是一个void*类型的,解引用之后就是void类型的,并不是我们想要的整形int,因此这个时候我们需要进行强制类型的转换,然后这个循环就可以使用ret控制次数了,这个时候的pthread_create函数得第四个参数的作用就凸显了出来;
6.创建两个线程
6.1创建方法
其实两个线程的创建并不是很复杂,我们只需要按照上面的,调用两次这个pthread_create函数即可,然后创建两个线程分别的join函数,其他的都是一样的;
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void *thread1_function(void *arg);
void *thread2_function(void *arg);
int count = 0;
int main(void)
{
pthread_t pthread1, pthread2;
int ret;
ret = pthread_create(&pthread1, NULL, thread1_function, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
ret = pthread_create(&pthread2, NULL, thread2_function, NULL);
if(ret != 0)
{
perror("pthread_create");
exit(1);
}
pthread_join(pthread1, NULL);
pthread_join(pthread2, NULL);
printf("The thread is over, process is over too.\n");
return 0;
}
void *thread1_function(void *arg)
{
printf("Thread1 begins running\n");
while(1)
{
printf("Thread1 count = %d\n", count++);
sleep(1);
}
return NULL;
}
void *thread2_function(void *arg)
{
printf("Thread2 begins running\n");
while(1)
{
printf("Thread2 count = %d\n", count++);
sleep(1);
}
return NULL;
}
6.2线程进程对比
我们上面的这个过程也可以说明一个问题:
因为这个多个线程是并发执行的,就是同一个变量,两个线程之间是可以相互看到的,但是对于两个进程而言,如果也是同一个变量,两个进程之间是相互独立的,一个进程对于另外一个进程没有影响,但是两个线程之间就不是独立的,他们可以共用一块空间,不然这个变量的数值怎么会连续增加呢?
因此,我们也可以看出来,这个线程之间想要保持通信其实并不难,因为他们的空间是一样的,但是对于进程而言,因为进程运行具有独立性,因此这个进程之间想要保持独立性,就没有线程之间那么简单!