第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。否则,编译时会出错。
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
补全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 ret = pthread_create(&thread, NULL, start_routine,arg);
/********** 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。否则,编译时会出错。
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
补全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 **********/
int pthread_join(pthread_t thread, void **waitThread);
if(pthread_join(thread, NULL) != 0)
/********** 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。否则,编译时会出错。
编程要求
本关的编程任务是补全右侧代码片段中Begin至End中间的代码,具体要求如下:
补全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;
}