首页 技术资料正文

Linux多线程编程(10分钟入门)(eos)

piaodoo 技术资料 2022-08-27 02:42:33 879 0

Linux多线程编程(10分钟入门)(eos)

Linux多线程编程(10分钟入门)

如今,几乎所有的电脑(操作系统)都支持同时执行多个任务,比如一边用迅雷下载资源,一边听歌,一边用 QQ 和好友聊天,这样的执行方式简称“并发”或者“并行”。

并发和并行都指的是计算机可以同时执行多个任务,但严格来讲,它们是有区别的,只是本节不对它们做更细致的区分。

程序并行的常用实现方式有两种,分别叫做“多进程编程”和“多线程编程”。本节,我们教大家如何在 Linux 下进行多线程编程。

程序、进程和线程

学习多线程编程的实现方法之前,首先要搞清楚什么是线程,这就要从程序、进程和线程三者的关系和区别讲起。程序以源文件的方式存储在外存(比如硬盘、U盘等)中,只有运行的时候才会被载入内存。对于支持并行的操作系统来说,必须为每一个运行的程序分配所需的资源(内存空间、输入输出设备等),并确保同时运行的程序之间不会相互干扰,为此,操作系统将每一个运行着的程序视为一个进程:
  • 操作系统以进程为单位,为每个进程分配执行所需要的资源;
  • 原则上,各个进程之间不允许访问对方的资源;
  • 操作系统实时监控着每个进程的执行状态,必要时可以强制其终止执行。
也就是说在操作系统看来,每个载入内存执行的程序都是一个进程。操作系统以进程为单位分配资源,各个进程相互独立,执行过程互不干扰。同一时间,操作系统可以运行多个应用程序(进程),每个应用程序(进程)还可以同时执行多个任务,例如迅雷支持同时下载多个文件,QQ 也支持同时和多个好友聊天。同一进程中,执行的每个任务都被视为一个线程。线程和进程之间的关系,与工厂和工人之间的关系非常相似。一个进程好比是一座工厂,一个线程就如同这个工厂中的一个工人。工厂可以容纳多个工人,每个工人负责完成一项具体的任务。工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源。也就是说,一个进程中可以包含多个线程,所有线程共享进程拥有的资源。当然,每个线程也可以拥有自己的私有资源。下图给您展示进程和线程之间的关系:图 1 进程和线程的关系如图 1 所示,所有线程共享的进程资源有:
  • 代码:即应用程序的代码;
  • 数据:包括全局变量、函数内的静态变量、堆空间的数据等;
  • 进程空间:操作系统分配给进程的内存空间;
  • 打开的文件:各个线程打开的文件资源,也可以为所有线程所共享,例如线程 A 打开的文件允许线程 B 进行读写操作。
各个线程也可以拥有自己的私有资源,包括寄存器中存储的数据、线程执行所需的局部变量(函数参数)等。

多线程编程的实现方法

了解了程序、进程和线程之间的关系后,多线程的含义就很容易理解了,它指的是一个进程中拥有多个(≥2)线程。通常,我们将编写多线程程序的过程称为“多线程编程”。

本文的目标立足于教会大家编写入门级别的多线程程序,有关线程同步、线程死锁、线程属性等内容,建议您转至《多线程编程(C语言+Linux)》专题做系统的学习。t恤的英文

Linux 上编写多线程程序,可以借助 头文件提供的一些函数,常用的函数有如下几个:

1) pthread_create()

pthread_create() 函数专门用来创建线程,语法格式如下:

int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);

各个参数的含义是:
  • thread:接收一个 pthread_t 类型变量的地址,每个 pthread_t 类型的变量都可以表示一个线程。
  • attr:手动指定新线程的属性,我们可以将其置为 NULL,表示新建线程遵循默认属性。
  • start_routine:以函数指针的方式指明新建线程需要执行哪个函数。
  • arg:向 start_routinue() 函数的形参传递数据。将 arg 置为 NULL,表示不传递任何数据。
如果成功创建线程,pthread_create() 函数返回数字 0,否则返回一个非零值。各个非零值都对应着不同的宏,指明创建失败的原因,常见的宏有以下几种:
  • EAGAIN:系统资源不足,无法提供创建线程所需的资源。
  • EINVAL:传递给 pthread_create() 函数的 attr 参数无效。
  • EPERM:传递给 pthread_create() 函数的 attr 参数中,某些属性的设置为非法操作,程序没有相关的设置权限。
以上这些宏都定义在 头文件中,如果想使用这些宏,需提前引入此头文件。

有关 pthread_create() 函数更详细的讲解,请阅读《创建线程》一文。t恤的英文

2) pthread_exit()

pthread_exit() 函数用于终止线程执行,语法格式如下:

void pthread_exit(void *retval);

retval 参数指向的数据将作为线程执行结束时的返回值,如果不需要返回任何数据,将其置为 NULL 即可。注意,retval 不能指向函数内部的局部变量,否则会导致程序运行出错甚至崩溃。

return 也可以终止线程执行,它和 pthread_exit() 之间有什么区别呢?我们已经在《终止线程(3种方法)》一文给出了答案。

3) pthread_cancel()

在多线程程序中,一个线程可以借助 pthread_cancel() 函数向另一个线程发送“终止执行”的信号。pthread_cancel() 函数的语法格式如下:

int pthread_cancel(pthread_t thread);

thread 参数用于指定接收信号的目标线程。当成功发送“终止执行”的信号时,函数返回值为 0,否则返回非零数。

再次强调,pthread_cancel() 函数只是向目标线程发送“终止执行”的信息,至于目标线程是否接收此信号,以及何时终止执行,由目标线程说了算,我们会在《终止线程执行,千万别踩这个坑!》一文做详细了解。t恤的英文

4) pthread_join()

pthread_join() 函数的功能主要有两个,分别是:
  1. 接收目标线程执行结束时的返回值;
  2. 释放目标线程占用的进程资源。
pthead_join() 函数的语法格式如下:

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

thread 参数用于指定目标线程;retval 参数用于存储接收到的返回值。实际场景中,调用 pthread_join() 函数可能仅是为了及时释放目标线程占用的资源,并不想接收它的返回值,这种情况下可以将 retval 置为 NULL。pthread_join() 函数会一直阻塞当前线程,直至目标线程执行结束,阻塞状态才会消除。如果成功等到了目标线程执行结束(成功获取到目标线程的返回值),pthread_join() 函数返回数字 0,否则返回非零数。

想全方位搞清楚 pthread_join() 函数的功能和用法,可阅读《获取线程函数的返回值》一文。t恤的英文

第一个多线程程序

接下来,我们利用上文学到的知识,编写第一个多线程程序:
include 
include 
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void* Thread1(void* arg)
{
    printf("http://www.weixueyuan.net\n");
    return "Thread1成功执行";
}
//定义线程要执行的函数,arg 为接收线程传递过来的数据
void* Thread2(void* arg)
{
    printf("魏雪原\n");
    return "Thread2成功执行";
}

int main()
{
    int res;
    //创建两个线程变量 
    pthread_t mythread1, mythread2;
    void* thread_result;
    //创建 mythread1 线程,执行 Thread1() 函数
    res = pthread_create(&mythread1, NULL, Thread1, NULL);
    if (res != 0) {
        printf("线程创建失败");
        return 0;
    }
    //创建 mythread2 线程,执行 Thread2() 函数
    res = pthread_create(&mythread2, NULL, Thread2, NULL);
    if (res != 0) {
        printf("线程创建失败");
        return 0;
    }
    //阻塞主线程,直至 mythread1 线程执行结束,用 thread_result 指向接收到的返回值,阻塞状态才消除。
    res = pthread_join(mythread1, &thread_result);
    //输出线程执行完毕后返回的数据
    printf("%s\n", (char*)thread_result);
    //阻塞主线程,直至 mythread2 线程执行结束,用 thread_result 指向接收到的返回值,阻塞状态才消除。
    res = pthread_join(mythread2, &thread_result);
    printf("%s\n", (char*)thread_result);
    printf("主线程执行完毕");
    return 0;
}
程序中共有 3 个线程,分别是主线程,mythread1 线程和 mythread2 线程。mythread1 线程负责执行 Thread1() 函数,mythread2 线程负责执行 Thread2() 函数。主线程先后调用了两次 pthread_join() 函数,都会阻塞主线程,直至 mythread1 和 mythread2 线程执行完毕,阻塞状态才会消除。假设程序存储在 thread.c 文件中,调用 GCC 编译此程序:

[root@localhost ~] gcc thread.c -o thread.exe -lpthread

最终会生成一个名为 thread.exe 的可执行文件,执行如下命令即可看到执行结果:

[root@localhost ~] ./thead.exehttp:www.weixueyuan.net魏雪原Thread1成功执行Thread2成功执行主线程执行完毕

总结

本节,我们了解了程序、进程和线程三者之间的关系,学会了如何编写一个简单的多线程程序。但是,与多线程编程相关的知识还有很多,比如实现线程同步,解决线程死锁问题、自定义线程的属性等,这些知识我们会在《多线程编程(C语言+Linux)》专题中给大家做详细的讲解。

版权声明:

本站所有资源均为站长或网友整理自互联网或站长购买自互联网,站长无法分辨资源版权出自何处,所以不承担任何版权以及其他问题带来的法律责任,如有侵权或者其他问题请联系站长删除!站长QQ754403226 谢谢。

有关影视版权:本站只供百度云网盘资源,版权均属于影片公司所有,请在下载后24小时删除,切勿用于商业用途。本站所有资源信息均从互联网搜索而来,本站不对显示的内容承担责任,如您认为本站页面信息侵犯了您的权益,请附上版权证明邮件告知【754403226@qq.com】,在收到邮件后72小时内删除。本文链接:https://www.piaodoo.com/119471.html

搜索