目录
第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 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
。否则,编译时会出错。
编程要求
本关的编程任务是补全右侧代码片段中
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 **********/ 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
。否则,编译时会出错。
编程要求
本关的编程任务是补全右侧代码片段中
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; }