• Linux系统编程

基础知识

  • 使用man命令,可以查看Linux手册,manmanual的命令缩写,其中文意思就是手册的意思。
  • 我们要查看一个命令函数的使用方法,我们就可以使用man 命令man 函数
  • 例如:我们要查看getpid的用法,就可以执行man getpid,就可以查看手册中getpid这个函数的用法

image-20250224012228588

image-20250224012456624

  • OS中进程是一个非常重要的概念,之后能让我们更方便理解进程,我们就先介绍另一个命令pstree,这个命令我们用来查看操作系统的进程树。我们可以使用进程树来辅助理解或者调试一些程序。pstree这个命令有一些相关的参数,接下来介绍一些常用的参数。

    • -p:输出进程树,并且会输出进程号
    • -a:显示进程树,并且显示每个进程对应的目录文件
  • 接下来我们使用pstree -p命令查看一下具体的进程树:

image-20250224013520032

fork()函数

  • 在理解fork函数之前我们先需要介绍一下进程,在操作系统中我们在运行中的程序就相当于一个进程,而每个进程都会有一个进程号(pid),并且进程具有树状关系,一个进程可以创建一个新进程,而这个新进程就相当于是该进程的一个子进程
  • 在一个进程中,我们可以使用getpid()获取当前进程所对应的进程号,我们还可以使用getppid()获取当前进程所对应父进程的进程号。

线程相关

pthread_create()函数

  • 会详细介绍pthread_create函数还会详细介绍pthread_join()函数

  • pthread_create(),这个函数是Linux系统提供的一个函数,作用是在当前进程中创建一个线程。接下来说明一下这个函数是如何使用的。

    • 首先要使用这个函数,首先要包含头文件#include<pthread.h>
    • 使用gcc编译链接的时候还需要使用-pthread命令来进行编译。
    • 这个函数的功能:在我们调用pthread_create()函数的进程中,就会在该进程中调用一个线程。这个线程会开始执行下面pthread_create()函数中第3个参数(即函数指针)指向的函数。
    • 注意:被指向的函数也有要求,这个函数参数要求传递void *,返回值也要求是void *,但是这并不意味着这个函数没有传递参数,具体请看下面例子,即指向void类型的指针。这个指针所指向的函数就是我们线程要执行的内容。
  • 现在说明一下这个函数的参数返回值

    • pthread_t类型:实际上该类型就是一个无符号整型,只是使用了这个语句进行定义typedef unsigned long int pthread_t;
    • pthread_t *thread:传入的是一个指向线程ID的指针。这也就是说,我们可以自定义线程ID,并不像创建一个子进程,这个进程号是系统给的。
    • pthread_attr_t *attrpthread_attr_t是一个结构体,所以*attr是结构体类型的指针,其指向的是pthread_attr_t。这个结构体指针,这个参数就是用来决定新线程的属性。如果该线程没有属性,就使用NULL作为参数。
    • void *(*start_routine)(void *)start_routine是一个函数指针,这个指针指向的函数地址就是线程要执行的内容。
    • void *arg:传递给线程函数的参数,没有任何参数就填NULL
    • 返回值:创建成功就会返回0,创建失败就会返回一个特定的非零值
1
2
int pthread_create
(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

level_1

  • 接下来举一个例子:
    • 就是创建一个线程,使得线程执行thread_function
    • 但是这个代码的执行结果不符合我们的预期,原因是进程创建完线程就结束了,进程结束会导致线程也被终止,线程是依赖于进程的,其资源属于进程
    • 所以这时我们需要在创建完线程后,让进程执行while循环,以确保进程不结束,这样就得到了预期结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// gcc 6_thread,c -pthread
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

void *thread_function(void *arg);

int main(void)
{
pthread_t pthread;
int ret;
ret = pthread_create(&pthread, NULL, thread_function, NULL);
if (ret != 0)
{
perror("pthread_create");
exit(1);
}
while(1); // 之后添加的
return 0;
}


void *thread_function(void *arg)
{
printf("Thread begins running\n");

while(1)
{
printf("Hello world\n");
sleep(1);
}
return NULL;
}

image-20250224190908851

level_2

  • 但是这个代码还是有问题,因为线程进入死循环了,现在我们继续修改这个代码,使得线程有限次循环。
  • 但是还会出现问题,这个线程结束后进程还会在等待中,这样程序一直不会结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// gcc 6_thread,c -pthread
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

void *thread_function(void *arg);

int main(void)
{
pthread_t pthread;
int ret;
ret = pthread_create(&pthread, NULL, thread_function, NULL);
if (ret != 0)
{
perror("pthread_create");
exit(1);
}
while(1); // 之后添加的
return 0;
}


void *thread_function(void *arg)
{
int i;
printf("Thread begins running\n");

for(i=0; i < 10 ; i++)
{
printf("Hello world\n");
sleep(1);
}
return NULL;
}

level_3

  • 接下来我们继续改进该程序,使得进程能够接受到线程结束。这个时候需要使用到pthread_join()这个函数。
    • 这个函数就是等待我们创建的线程结束。线程结束,这个函数将立刻返回;线程没结束,这个函数将阻塞进程。
    • 这个函数有两个参数和int类型的返回值int pthread_join(pthread_t thread, void **retval);
      • pthread_t thread线程IDpthread_join要等待什么线程结束
      • void **retval结束状态,如果这个指针不是空指针,将保存目标线程的退出状态,如果不想保存就使用NULL
      • 返回值:调用成功返回0
  • 接下来继续优化代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// gcc 6_thread,c -pthread
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

void *thread_function(void *arg);

int main(void)
{
pthread_t pthread;
int ret;
ret = pthread_create(&pthread, NULL, thread_function, NULL);
if (ret != 0)
{
perror("pthread_create");
exit(1);
}
pthread_join(pthread,NULL);
printf("Thre thread is over,process is over too.\n");
return 0;
}


void *thread_function(void *arg)
{
int i;
printf("Thread begins running\n");

for(i = 0;i < 10; i++)
{
printf("Hello world\n");
sleep(1);
}
return NULL;
}

image-20250224192455330

level4

  • 接下来我们继续改进该程序,让我们的进程指定线程循环的次数。这时就需要用上pthread_create()函数的最后一个参数* argv

  • 但是如果我们按照正常指针来使用这个参数,在编译的时候就会发生错误,因为传递的是void类型的指针,这时需要转换为整型类型的指针

image-20250224193032681

image-20250224193053738

  • 最终代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// gcc 6_thread,c -pthread
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

void *thread_function(void *arg);

int main(void)
{

pthread_t pthread;
int ret;
int count = 5;
ret = pthread_create(&pthread, NULL, thread_function, &count);
if (ret != 0)
{
perror("pthread_create");
exit(1);
}
pthread_join(pthread,NULL);
printf("Thre thread is over,process is over too.\n");
return 0;
}


void *thread_function(void *arg)
{
int i;
printf("Thread begins running\n");

for(i = 0;i < *(int *)arg; i++)
{
printf("Hello world\n");
sleep(1);
}
return NULL;
}

image-20250224193322794

多线程

线程之间通信