C语言线程

C语言线程C语言线程入门介绍,包括如何创建线程,退出线程,获取线程返回的结果。


一、什么是线程?

说到线程之前,我们不得不说到进程,进程指的是一个正在执行的应用程序。而线程的功能是执行应用程序中的某个具体的任务,比如一段程序,一个函数等。

线程和进程之间的关系,类似于工厂和工人之间的关系,进程就是工厂,线程是工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(生产原料、电力、食堂等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们互相配合,共同保证整个工厂的平稳运行。

每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间等。一个进程至少包含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);

参数含义

  1. pthread_t *thread:传递一个pthread_t类型的指针变量,也可以直接传递某个pthread_t类型变量的地址。pthread_t是一种用于表示线程的数据类型,每一个pthread_t类型的变量都可以表示一个线程。

  2. const pthread_attr_t *attr:用于手动设置新建线程的属性,例如线程的调用策略、线程所能使用的栈内存大小等。大部分场景中,我们不需要手动修改线程的属性,将attr参数赋值为NULL,pthread_create()函数会采用系统默认的属性值创建线程。

  3. void *(*start_routine) (void *):以函数指针的方式指明新建线程需要执行的函数,该函数的参数最多有1个(可以省略不写),形参和返回值的类型都必须为void *类型。void *类型又称空指针类型,表明指针所指数据的类型是未知的。使用此类型指针时,通常需要先对其进行强制类型转换,然后才能正常访问指针指向的数据。

  4. 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。

总结

以上就是今天要讲的内容,本文仅仅简单介绍了线程的创建,终止以及如何获取线程函数的返回值。

链接: http://c.biancheng.net/thread/what-is-thread.html

今天的文章C语言线程分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/24237.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注