文章目录
一、什么是线程?
说到线程之前,我们不得不说到进程,进程指的是一个正在执行的应用程序。而线程的功能是执行应用程序中的某个具体的任务,比如一段程序,一个函数等。
线程和进程之间的关系,类似于工厂和工人之间的关系,进程就是工厂,线程是工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(生产原料、电力、食堂等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们互相配合,共同保证整个工厂的平稳运行。
每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间等。一个进程至少包含1个线程,可以包含多个线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。
进程仅负责为各个线程提供所需的资源,真正执行任务的是线程,而不是进程。
二、什么是多线程?
所谓多线程,即一个进程中拥有多个(>=2)线程,线程之间相互协作,共同执行一个应用程序。
很多应用程序都是多线程程序,例如QQ具备同时和多人聊天的能力、迅雷具备同时下载多个资源的能力。
三、创建线程pthread_create()
1.函数声明
声明在头文件<pthread.h>中,格式如下:
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
参数含义
-
pthread_t *thread:传递一个pthread_t类型的指针变量,也可以直接传递某个pthread_t类型变量的地址。pthread_t是一种用于表示线程的数据类型,每一个pthread_t类型的变量都可以表示一个线程。
-
const pthread_attr_t *attr:用于手动设置新建线程的属性,例如线程的调用策略、线程所能使用的栈内存大小等。大部分场景中,我们不需要手动修改线程的属性,将attr参数赋值为NULL,pthread_create()函数会采用系统默认的属性值创建线程。
-
void *(*start_routine) (void *):以函数指针的方式指明新建线程需要执行的函数,该函数的参数最多有1个(可以省略不写),形参和返回值的类型都必须为void *类型。void *类型又称空指针类型,表明指针所指数据的类型是未知的。使用此类型指针时,通常需要先对其进行强制类型转换,然后才能正常访问指针指向的数据。
-
void *arg:指定传递给start_routine函数的实参,当不需要传递任何数据时,将arg赋值为NULL即可。
返回值
如果成功创建线程,pthread_create()函数返回数字0,否则返回非零值。各个非零值都对应着不同的宏(声明在<errno.h>头文件中),指明创建失败的原因,常见的宏有以下几种:
- EAGAIN:系统资源不足,无法提供创建线程所需的资源。
- EINVAL:传递给 pthread_create() 函数的 attr参数无效。
- EPERM:传递给 pthread_create() 函数的 attr 参数中,某些属性的设置为非法操作,程序没有相关的设置权限。
2.用法示例
代码如下:
#include <stdio.h>
#include <unistd.h> //调用 sleep() 函数
#include <pthread.h> //调用 pthread_create() 函数
void *ThreadFun(void *arg)
{
if (arg == NULL) {
printf("arg is NULL\n");
}
else {
printf("%s\n", (char*)arg);
}
return NULL;
}
int main()
{
int res;
char * url = "hello world!";
//定义两个表示线程的变量(标识符)
pthread_t myThread1,myThread2;
//创建 myThread1 线程
res = pthread_create(&myThread1, NULL, ThreadFun, NULL);
if (res != 0) {
printf("线程创建失败");
return 0;
}
sleep(5); //令主线程等到 myThread1 线程执行完成
//创建 myThread2 线程
res = pthread_create(&myThread2, NULL, ThreadFun,(void*)url);
if (res != 0) {
printf("线程创建失败");
return 0;
}
sleep(5); // 令主线程等到 mythread2 线程执行完成
return 0;
}
运行结果
arg is NULL
hello world!
程序中共创建了 2 个线程,分别命名为 myThread1 和 myThread2。myThread1 和 myThread2 线程执行的都是 threadFun() 函数,不同之处在于,myThread1 线程没有给 threadFun() 函数传递任何数据,而 myThread2 线程向 threadFun() 函数传递了 hello world!” 这个字符串。
从程序的执行过程不难看出, pthread_create() 函数成功创建的线程会自动执行指定的函数,不需要手动开启。此外,为了确保创建的线程能在主线程之前执行完,程序中调用 sleep() 函数延缓了主线程的执行速度。
四、终止线程执行pthread_exit()
1.函数声明
声明在头文件<pthread.h>中,格式如下:
void pthread_exit(void *retval);
参数含义
- retval 是void*类型的指针,可以指向任何类型的数据,它指向的数据将作为线程退出时的返回值。如果线程不需要返回任何数据,将 retval 参数置为NULL即可。
2.用法示例
代码如下:
#include <stdio.h>
#include <pthread.h>
//线程要执行的函数,arg 用来接收线程传递过来的数据
void *ThreadFun(void *arg)
{
//终止线程的执行,将“http://c.biancheng.net”返回
pthread_exit("hello world!"); //返回的字符串存储在常量区,并非当前线程的私有资源
printf("*****************");//此语句不会被线程执行
}
int main()
{
int res;
//创建一个空指针
void * thread_result;
//定义一个表示线程的变量
pthread_t myThread;
res = pthread_create(&myThread, NULL, ThreadFun, NULL);
if (res != 0) {
printf("线程创建失败");
return 0;
}
//等待 myThread 线程执行完成,并用 thread_result 指针接收该线程的返回值
res = pthread_join(myThread, &thread_result);
if (res != 0) {
printf("等待线程失败");
}
printf("%s", (char*)thread_result);
return 0;
}
运行结果
hello world!
不难看出,myThread 线程并没有执行 ThreadFun() 函数中最后一个 printf() 语句,从侧面验证了 pthread_exit() 函数的功能。此外,我们通过在主线程(main() 函数)调用 pthread_join() 函数,获取到了 myThread 线程返回的数据。
3.pthread_exit()和return语句的区别
- return 的含义是返回,它不仅可以用于线程执行的函数,普通函数也可以使用;
- pthread_exit() 函数的含义是线程退出,它专门用于结束某个线程的执行。
- return 语句不仅会终止主线程执行,还会终止其它子线程执行。
- pthread_exit() 函数只会终止当前线程,不会影响其它线程的执行。
五、获取线程函数的返回值pthread_join()
1.函数声明
声明在头文件<pthread.h>中,格式如下:
int pthread_join(pthread_t thread, void ** retval);
参数含义
- thread 参数用于指定接收哪个线程的返回值;
- retval 参数表示接收到的返回值,如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值,可以将 retval 参数置为 NULL。
返回值
pthread_join() 函数成功等到了目标线程执行结束(成功获取到目标线程的返回值),返回值为数字 0;反之如果执行失败,函数会根据失败原因返回相应的非零值,每个非零值都对应着不同的宏,例如:
- EDEADLK:检测到线程发生了死锁。
- EINVAL:分为两种情况,要么目标线程本身不允许其它线程获取它的返回值,要么事先就已经有线程调用 pthread_join() 函数获取到了目标线程的返回值。
- ESRCH:找不到指定的 thread 线程。
强调:一个线程执行结束的返回值只能由一个 pthread_join() 函数获取,当有多个线程调用 pthread_join() 函数获取同一个线程的执行结果时,哪个线程最先执行 pthread_join() 函数,执行结果就由那个线程获得,其它线程的 pthread_join() 函数都将执行失败。
对于一个默认属性的线程 A 来说,线程占用的资源并不会因为执行结束而得到释放。而通过在其它线程中执行pthread_join(A,NULL);语句,可以轻松实现“及时释放线程 A 所占资源”的目的。
2.用法示例
代码如下:
#include <stdio.h>
#include <errno.h> //使用宏 ESRCH
#include <pthread.h>
//线程执行的函数
void *ThreadFun(void *arg)
{
pthread_exit("hello world!");
}
int main()
{
int res;
void * thread_result;
pthread_t myThread;
//创建 myThread 线程
res = pthread_create(&myThread, NULL, ThreadFun, NULL);
if (res != 0) {
printf("线程创建失败");
return 0;
}
//阻塞主线程,等待 myThread 线程执行结束
res = pthread_join(myThread, &thread_result);
if (res != 0) {
printf("1:等待线程失败");
}
//输出获取到的 myThread 线程的返回值
printf("%s\n", (char*)thread_result);
//尝试再次获取 myThread 线程的返回值
res = pthread_join(myThread, &thread_result);
if (res == ESRCH) {
printf("2:等待线程失败,线程不存在");
}
return 0;
}
运行结果
hello world!
2:等待线程失败,线程不存在
在程序的在主线程(main() 函数)中,我们尝试两次调用 pthread_join() 函数获取 myThread 线程执行结束的返回值。通过执行结果可以看到,第一个 pthread_join() 函数成功执行,而第二个 Pthread_join() 函数执行失败。原因很简单,第一个成功执行的 pthread_join() 函数会使 myThread 线程释放自己占用的资源,myThread 线程也就不复存在,所以第二个 pthread_join() 函数会返回 ESRCH。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了线程的创建,终止以及如何获取线程函数的返回值。
今天的文章C语言线程分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/24237.html