Linux之线程管理

发布于:2024-04-27 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

第1关:创建线程

任务描述

相关知识

使用pthread_create函数创建线程

编程要求

答案:

第2关:线程挂起

任务描述

相关知识

使用pthread_join挂起线程

编程要求

答案:

第3关:线程终止

任务描述

相关知识

使用pthread_exit退出线程

使用pthread_cancel退出线程

编程要求

答案: 


第1关:创建线程

任务描述

通常我们编写的程序都是单进程,如果在一个进程中没有创建新的线程,则这个单进程程序也就是单线程程序。本关我们将介绍如何在一个进程中创建多个线程。

本关任务:学会使用C语言在Linux系统中使用pthread_create库函数创建一个新的线程。

相关知识

通常我们编写的程序都是单线程程序,单线程的程序都是按照一定的顺序按序的执行。有些情况下,我们需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场。例如,我们需要实现一个在线音乐播放器,一方面我们在线播放用户选中的音乐,另一方面又需要同时下载曲子,这些任务需要同时被执行,而不是按序一个一个的执行,这样才会使得用户一边播放音乐,一边下载自己喜欢的曲子。针对以上需求,我们可以用多线程实现,一个线程专门在线播放用户选中的音乐,另外一个线程专门用户下载曲子。

通常,一个进程只包含一个线程,我们把这个线程叫做主线程,例如main函数就是一个主线程。如果在主线程里创建多个线程,那么程序就会在创建线程的地方产生分支,变成了多个程序来同时运行。这似乎和我们以前学习的多进程一样,其实背后的原理还是有所区别。

在多进程中,子进程是通过拷贝父进程的地址空间来实现,而在多线程中,同一进程中的所有线程都是共享程序代码,一段代码可以被多个线程来执行。

Linux系统中,我们可以通过pthread_create函数来创建线程。我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 pthread_create

使用pthread_create函数创建线程

pthread_create函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>

函数格式如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
thread:该参数是一个指针,当线程创建成功后,用来返回创建的线程ID;
attr:该参数用于指定线程的属性,NULL表示使用默认属性,通常我们使用默认属性;
start_routine:该参数为一个函数指针,指向线程创建后要调用的函数,也被称为线程函数;
arg:该参数指向传递给线程函数的参数;

函数返回值说明: 调用成功,pthread_create返回值为0;调用失败返回一个非零的值。

注意:

pthread_create一旦调用成功,新创建的线程将开始运行第3个参数所指向的函数,原来的线程继续往下运行。由于线程是第三库所提供的,因此在编译包含线程的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:

编写一个程序,使用pthread_create函数创建一个线程,在新创建的线程中打印一个字符串,在主线程中也打印一个字符串。详细代码如下所示:
 

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    printf("This is My first thread\n");
    
    return NULL;
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    sleep(1);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为createThread.c文件,编译执行。可以看到新创建的线程被调用成功。

案例演示2:

编写一个程序,使用pthread_create函数创建两个线程,并在每个线程中接受主线程传来的字符串,并将其打印出来。详细代码如下所示:
 

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    printf("print String: %s\n", (char *)arg);
    return NULL;
}
int main()
{
    pthread_t thread1, thread2;
    int ret = pthread_create(&thread1, NULL, printString, "This is first thread");
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    ret = pthread_create(&thread2, NULL, printString, "This is second thread");
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    sleep(1);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为printThread.c文件,编译执行。可以看到新创建的按照主线程传递的参数,将指定的字符串打印出来。

注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

补全createThread函数,使用pthread_create函数创建线程,并将start_routine作为线程处理函数,arg作为线程处理函数的参数,同时将创建成功的线程ID作为createThread函数的返回值。

答案:
#include <stdio.h>
#include <pthread.h>

/************************
 * 参数start_routine: 函数指针,用于指向线程函数
 * 参数arg: 是线程函数的参数
 * 返回值: 返回线程ID
*************************/
pthread_t createThread(void *(*start_routine) (void *), void *arg)
{
	pthread_t thread;
	/********** BEGIN **********/
	int res = pthread_create(&thread, NULL, start_routine, arg);
    if (res != 0)
    {
        return 0;
    }
	/********** END **********/

	return thread;
}

第2关:线程挂起

任务描述

在学习多进程编程的时候,我们学习了如何等待一个进程结束,那么在多线程中也存在同样的操作,如何使得一个线程挂起等待其他的线程先执行。本关我们将介绍如何挂起一个线程,并等待指定线程。

本关任务:学会使用C语言在Linux系统中使用pthread_join库函数挂起当前线程,并等待指定的线程。

相关知识

通过上一管卡的学习,我们学会了如何创建一个线程。在上一关中我们遗留了一个未解决的问题,不知道细心的你发现没,那就是我们在案例演示中使用了sleep函数给主线程睡眠了1秒,如果主线程先退出,那么新创建的线程会发生什么?正确答案是,如果主线程先退出,那么还未执行完成的其他所有线程将被终止。因此,保证主线程最后一个退出是非常重要的。

接下来,我们用实例验证如果主线程先退出,那么其他的线程会不会受到影响,将上一关中的案例一修改成如下:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    sleep(1);
    printf("This is My first thread\n");
    
    return NULL;
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    printf("This is main thread\n");
    
    return 0;
}

编译执行。可以看到当主线程退出后,新创建的线程是不会继续执行的。

Linux系统中提供了挂起当前线程,用来等待一个指定线程结束的库函数pthread_join。这个函数相当于进程等待函数waitpid,他可以挂起当前线程,并一直等待指定线程,直到指定线程退出后,该函数才会返回继续执行。

Linux系统中,我们可以通过pthread_join函数来挂起线程。我们可以使用man命令来查询该函数的使用方法。具体的查询命令为:man 3 pthread_join

使用pthread_join挂起线程

pthread_join函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>

函数格式如下:

int pthread_join(pthread_t thread, void **retval);

参数说明:

thread:该参数是一个线程ID,用于指定要等待其终止的线程;
retval:该参数用于存放等待线程的返回值,如果不关注线程的退出值,则可以设置为NULL;

函数返回值说明: 调用成功,pthread_join返回值为0;调用失败返回一个非零的值。

注意:

由于线程是第三库所提供的,因此在编译包含线程操作的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:

编写一个程序,使用pthread_join函数挂起当前线程,等待新创建的线程先执行。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    sleep(1);
    printf("This is My first thread\n");
    
    return NULL;
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    if(pthread_join(thread, NULL) != 0)
    {
        printf("等待线程失败\n");
        return -1;
    }
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为joinThread.c文件,编译执行。可以看到尽管新创建的线程睡眠了1秒,然后还是被正常的运行完。 注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

补全waitThread函数,使用pthread_join函数挂起当前线程,等待指定线程结束,thread为要等待的线程ID号,waitThread函数等待线程成功返回0,失败返回-1

答案:
#include <stdio.h>
#include <pthread.h>

/************************
 * 参数thread: 需要等待结束的线程ID号
 * 返回值: 等待成功返回0,失败返回-1
 * 提示: 忽略线程返回值
*************************/
int waitThread(pthread_t thread)
{
	int ret = -1;
	/********** BEGIN **********/
	ret = pthread_join(thread, NULL);
	/********** END **********/

	return ret;
}

第3关:线程终止

 

任务描述

在学习多进程编程的时候,我们知道进程的退出有很多中方式,常见的有exit函数,而线程的退出也有多种方法。本关我们将介绍如何终止一个线程的执行。

本关任务:学会使用C语言在Linux系统中终止一个线程。

相关知识

Linux下有三种方式可以是一个线程终止,分别是:(1)通过return从线程函数返回;(2)通过调用pthread_exit使得一个线程退出;(3)通过调用pthread_cancel终止一个线程;

有两种特殊情况要注意:第一种情况就是,在主线程中,如果从main函数返回或者是调用exit函数来终止主线程的执行,则整个进程将终止执行,此时进程中的所有线程也将被终止执行,因此,在主线程中不能过早的退出,这就是我们上一关中所介绍的为什么要使用pthread_join函数来挂起主线程的原因。另一种情况就是,如果在主线程中调用pthread_exit函数终止主线程的执行,则仅仅是主线程消亡,进程是不会被终止的,因此进程内的其他线程也不会被终止,直到所有线程执行结束,进程才会被终止。

在上一关中,我们学习了pthread_join函数等待一个线程的结束,并获取线程退出值,那么线程以不同的方法终止,通过pthread_join得到的退出值也是不同的,总结如下:
 

如果线程通过调用return返回,则pthread_join所得到的退出值就是线程函数的return的值;
如果线程是通过调用pthread_cancel异常终止,则pthread_join所得到的退出值是PTHREAD_CANCELED;
如果线程是通过调用pthread_exit异常终止,则pthread_join所得到的退出值就是pthread_exit函数的参数值;

我们可以使用man命令来查询这些函数的使用方法。具体的查询命令为:man 3 函数名

使用pthread_exit退出线程

pthread_exit函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>

函数格式如下:

void pthread_exit(void *retval);

参数说明:

retval:线程的返回值;

函数返回值说明: 无返回值。

注意:

由于线程是第三库所提供的,因此在编译包含线程操作的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:

编写一个程序,使用pthread_exit函数退出线程,并使用pthread_join函数获取线程的退出值。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    sleep(1);
    printf("This is My first thread\n");
    pthread_exit("thread finished");
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    void *status = NULL;
    if(pthread_join(thread, &status) != 0)
    {
        printf("等待线程失败\n");
        return -1;
    }
    printf("first thread exit(%s)\n", (char *)status);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为exitThread.c文件,编译执行。可以看到新创建的线程退出的代码为:"thread finished",并且在主线程中使用pthread_join函数成功的获取到退出代码。 注意:编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

使用pthread_cancel退出线程

pthread_cancel函数的具体的说明如下:

需要的头文件如下:

#include <pthread.h>

函数格式如下:

int pthread_cancel(pthread_t thread);

参数说明:

thread:需要被取消运行的线程ID;

函数返回值说明: 调用成功,返回0,调用失败,返回一个非零值。

注意:

由于线程是第三库所提供的,因此在编译包含线程操作的程序时,我们需要手动链接线程库,只需在编译命令加上-lpthread参数即可。

案例演示1:

编写一个程序,使用pthread_cancel函数退出线程,并使用pthread_join函数获取线程的退出值。详细代码如下所示:

#include <stdio.h>
#include <pthread.h>
void *printString(void *arg)
{
    while(1)
    {
        printf("This is My first thread\n");
        sleep(1);
    }
}
int main()
{
    pthread_t thread;
    int ret = pthread_create(&thread, NULL, printString, NULL);
    if(ret != 0)
    {
        printf("创建线程失败\n");
        return -1;
    }
    
    sleep(2);
    ret = pthread_cancel(thread);
    if(ret != 0)
    {
        printf("cancel thread(%lu) failure\n", thread);
        return -1;
    }
    void *status = NULL;
    if(pthread_join(thread, &status) != 0)
    {
        printf("等待线程失败\n");
        return -1;
    }
    printf("first thread exit(%d)\n", (int)status);
    printf("This is main thread\n");
    
    return 0;
}

将以上代码保存为cancelThread.c文件,编译执行。可以看到新创建的线程被主线程使用pthread_cancel函数强制取消执行,并且退出的代码为:-1,也就是PTHREAD_CANCELED在Linux系统中的定义为-1

注意:

编译程序的时候需要手动加上线程库-lpthread。否则,编译时会出错。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

补全cancelThread函数,使用pthread_cancel函数终止指定的线程,thread为线程要被取消的线程ID号,调用成功返回0,否则返回-1

答案: 
#include <stdio.h>
#include <pthread.h>

/************************
 * 参数thread: 需要等待结束的线程ID号
 * 返回值: 等待成功返回0,失败返回-1
 * 提示: 忽略线程返回值
*************************/
int cancelThread(pthread_t thread)
{
	int ret = -1;
	/********** BEGIN **********/
    ret = pthread_cancel(thread);
	/********** END **********/

	return ret;
}