3

Linux系统编程-(pthread)线程创建与使用 - InfoQ 写作平台

 2 years ago
source link: https://xie.infoq.cn/article/83e19893bfa708eed55cb5439
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Linux 系统编程 -(pthread) 线程创建与使用

前面文章介绍了 Linux 下进程的创建、管理、使用、通信,了解了多进程并发;这篇文章介绍 Linux 下线程的基本使用。

线程与进程的区别(1)进程: 是操作系统调度最小单位。Linux 下可以通过 ps、top 等命令查看进程的详细信息。(2)线程: 是进程调度的最小单位,每个进程都有一个主线程。在进程里主要做事情就是线程。

(3)在全系统中,进程 ID 是唯一标识,对于进程的管理都是通过 PID 来实现的。每创建一个进程,内核去中就会创建一个结构体来存储该进程的全部信息,每一个存储进程信息的节点也都保存着自己的 PID。需要管理该进程时就通过这个 ID 来实现(比如发送信号)。当子进程结束要回收时(子进程调用 exit()退出或代码执行完),需要通过 wait()系统调用来进行,未回收的消亡进程会成为僵尸进程,其进程实体已经不复存在,但会虚占 PID 资源,因此回收是有必要的。

对于线程而言,若要主动终止需要调用 pthread_exit() ,主线程需要调用 pthread_join()来回收(前提是该线程没有设置 “分离属性”)。像线发送线程信号也是通过线程 ID 实现

进程间的通信方式:A.共享内存 B.消息队列 C.信号量 D.有名管道 E.无名管道 F.信号 G.文件 H.socket 线程间的通信方式:A.互斥量 B.自旋锁 C.条件变量 D.读写锁 E.线程信号 F.全局变量

进程间采用的通信方式要么需要切换内核上下文,要么要与外设访问(有名管道,文件)。所以速度会比较慢。而线程采用自己特有的通信方式的话,基本都在自己的进程空间内完成,不存在切换,所以通信速度会较快。也就是说,进程间与线程间分别采用的通信方式,除了种类的区别外,还有速度上的区别。

说明: 当运行多线程的进程捕获到信号时,只会阻塞主线程,其他子线程不会影响会继续执行。

2. 线程相关函数介绍

2.1 创建线程

pthread_create 是 Unix 操作系统(Unix、Linux 等)的创建线程的函数。编译时需要指定链接库:-lpthread 函数原型

#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数介绍

第一个参数为指向线程标识符的指针。第二个参数用来设置线程属性。默认可填 NULL。第三个参数是线程运行函数的起始地址。最后一个参数是运行函数的参数。不需要参数可填 NULL。Linux 下查看函数帮助:# man pthread_create

返回值:若线程创建成功,则返回 0。若线程创建失败,则返回出错编号。线程创建成功后, attr 参数用于指定各种不同的线程属性。新创建的线程从 start_rtn 函数的地址开始运行,该函数只有一个万能指针参数 arg,如果需要向线程工作函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为 arg 的参数传入。

示例:

#include <stdio.h>#include <pthread.h>//线程函数1void *pthread_func1(void *arg){  while(1)  {    printf("线程函数1正在运行.....\n");    sleep(2);  }}//线程函数2void *pthread_func2(void *arg){  while(1)  {    printf("线程函数2正在运行.....\n");    sleep(2);  }}int main(int argc,char **argv){  pthread_t thread_id1;  pthread_t thread_id2;   /*1. 创建线程1*/    if(pthread_create(&thread_id1,NULL,pthread_func1,NULL))  {    printf("线程1创建失败!\n");    return -1;  }  /*2. 创建线程2*/    if(pthread_create(&thread_id2,NULL,pthread_func2,NULL))  {    printf("线程2创建失败!\n");    return -1;  }  /*3. 等待线程结束,释放线程的资源*/  pthread_join(thread_id1,NULL);  pthread_join(thread_id2,NULL);  return 0;}//gcc pthread_demo_code.c -lpthread

2.2 退出线程

线程通过调用 pthread_exit 函数终止执行,就如同进程在结束时调用 exit 函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。

这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针,该返回值可以通过 pthread_join 函数的第二个参数得到。

函数原型

#include <pthread.h>void pthread_exit(void *retval);

参数解析线程的需要返回的地址。注意: 线程结束必须释放线程堆栈,就是说线程函数必须调用 pthread_exit()结束,否则直到主进程函数退出才释放

2.3 等待线程结束

pthread_join()函数,以阻塞的方式等待 thread 指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且 thread 指定的线程必须是 joinable(结合属性)属性。函数原型

#include <pthread.h>int pthread_join(pthread_t thread, void **retval);

参数第一个参数: 线程标识符,即线程 ID,标识唯一线程。最后一个参数: 用户定义的指针,用来存储被等待线程返回的地址。返回值 0 代表成功。 失败,返回的则是错误号。接收线程返回值示例:

//退出线程pthread_exit ("线程已正常退出");//接收线程的返回值void *pth_join_ret1;pthread_join( thread1, &pth_join_ret1);

2.4 线程分离属性

创建一个线程默认的状态是 joinable(结合属性),如果一个线程结束运行但没有调用 pthread_join,则它的状态类似于进程中的 Zombie Process(僵死进程),即还有一部分资源没有被回收(退出状态码),所以创建线程者应该 pthread_join 来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于进程的 wait,waitpid)。但是调用 pthread_join(pthread_id)函数后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。

pthread_detach 函数可以将该线程的状态设置为 detached(分离状态),则该线程运行结束后会自动释放所有资源。函数原型

#include <pthread.h>int pthread_detach(pthread_t thread);

参数线程标识符返回值 0 表示成功。错误返回错误码。EINVAL 线程并不是一个可接合线程。ESRCH 没有线程 ID 可以被发现。

2.5 获取当前线程的标识符

pthread_self 函数功能是获得线程自身的 ID。函数原型

#include <pthread.h>pthread_t pthread_self(void);

返回值当前线程的标识符。pthread_t 的类型为 unsigned long int,所以在打印的时候要使用 %lu 方式,否则显示结果出问题。

2.6 自动清理线程资源

线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序。用于程序异常退出的时候做一些善后的资源清理。在 POSIX 线程 API 中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数用于自动释放资源。从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和异常终止)都将执行pthread_cleanup_push()所指定的清理函数。

注意:pthread_cleanup_push函数与pthread_cleanup_pop函数需要成对调用。函数原型

void pthread_cleanup_push(void (*routine)(void *),void *arg); //注册清理函数void pthread_cleanup_pop(int execute); //释放清理函数

参数 void (*routine)(void *) :处理程序的函数入口。void *arg :传递给处理函数的形参。int execute:执行的状态值。 0 表示不调用清理函数。1 表示调用清理函数。

导致清理函数调用的条件:

  1. 调用 pthread_exit()函数

  2. pthread_cleanup_pop 的形参为 1。注意:return 不会导致清理函数调用。

2.7 自动清理线程示例代码

#include <stdio.h>#include <pthread.h>#include <stdlib.h>//线程清理函数void routine_func(void *arg){   printf("线程资源清理成功\n");}//线程工作函数void *start_routine(void *dev){   pthread_cleanup_push(routine_func,NULL);  //终止线程  // pthread_exit(NULL);   pthread_cleanup_pop(1); //1会导致清理函数被调用。0不会调用。}int main(int argc,char *argv[]){   pthread_t thread_id;  //存放线程的标识符  /*1. 创建线程*/  if(pthread_create(&thread_id,NULL,start_routine,NULL)!=0)  {     printf("线程创建失败!\n");      }   /*2.设置线程的分离属性*/  if(pthread_detach(thread_id)!=0)  {     printf("分离属性设置失败!\n");  }  while(1){}    return 0;  }

2.8 线程取消函数

pthread_cancel 函数为线程取消函数,用来取消同一进程中的其他线程。

头文件: #include <pthread.h>函数原型:pthread_cancel(pthread_t tid);

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK