Linux系统编程——线程的基本操作

线程控制原语

线程函数的程序在 pthread 库中,故链接时要加上参数 。 -lpthread 选项

在线程中检查错误的方法应为

fprintf(stderr, "xxx error: %s\n", strerror(ret));

查看线程ID

作用: 查看当前线程ID

函数原型: pthread_t pthread_self(void);

返回值: 线程ID

线程 ID:pthread_t 类型,在 Linux 下为无符号整数数%lu,在其他系统中可能是结构体实现
线程 ID 是 进程内部的识别标志,两个进程间,线程 ID 允许相同。

线程创建

函数原型:

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

参数:

  • thread:传出参数,表示线程标识符的地址
  • attr:线程属性结构体地址,通常为 NULL ,代表使用默认属性
  • start_routine:子线程回调函数的入口地址,创建成功,在 pthread_craete函数返回时,该函数会被自动调用
  • arg:传给线程函数的参数,没有就传 NULL

返回值:

  • 成功:返回 0
  • 失败:直接返回 errno

pthread_create()创建的线程从指定的回调函数开始运行,该函数运行完后,该线程也就退出了。线程依赖进程存在的,共享进程的资源,如果创建线程的进程结束了,线程也就结束了。

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

void *tun(void *arg){
    printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
    return NULL;
}

int main(int argc, char *argv[]){
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, tun, NULL); //创建子线程,使其执行 tuc 函数
    if(ret != 0){ 
        fprintf(stderr, "pthread_create error %s\n", strerror(ret));
    }   

    printf("this is main pid is %d pid is %lu\n", getpid(), pthread_self());

    sleep(1); //防止主线程执行结束释放了进程
    return 0;
}

循环创建多个线程

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>


void *tun(void *arg){
    int i = (int)(long)arg; //与下面同理
    sleep(i); //保证输出的顺序
    printf("I'm %dth this is thread pid is %d tid is %lu\n",i+1, getpid(), pthread_self());
    return NULL;
}


int main(int argc, char *argv[]){
    pthread_t tid;
    int i = 0;
    for(i = 0;i < 5;i++){
        int ret = pthread_create(&tid, NULL, tun, (void *)(long)i);//这里传值时由于指针为 8字节,所以先将 i 转化为 8字节数据类型,然后再转为 (void*)类型。
        if(ret != 0){ 
            fprintf(stderr, "pthread_create error %s\n", strerror(ret));
        }
    } 

    sleep(6);
    printf("this is main pid is %d pid is %lu\n", getpid(), pthread_self());

    return 0;
}

注意:在 pthread_create 中第四个参数在传一个变量时,此处不能传 &i,因为 i 是变化的当在子线程的函数中去访问 i 的地址时,i的值已经发生了改变。

线程退出

在进程中我们可以调用 exit() 函数或 _exit() 函数来结束进程,在一个线程中我们可以通过 pthread_exit() 在不终止整个进程的情况下停止它的控制流。

作用::退出调用线程。=,一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放

函数原型: void pthread_exit(void *retval);

参数:

  • retval:存储线程退出状态的指针,无退出值时,NULL。

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

void *fun(){
    return NULL;
}

void *tun(void *arg){
    int i = (int)(long)arg;
    sleep(i);
    if(i == 2){ 
        //exit(0);
        //fun();
        pthread_exit(NULL);
    }   
    printf("I'm %dth this is thread pid is %d tid is %lu\n",i+1, getpid(), pthread_self());
    return NULL;
}


int main(int argc, char *argv[]){
    pthread_t tid;
    int i = 0;
    for(i = 0;i < 5;i++){
        int ret = pthread_create(&tid, NULL, tun, (void *)(long)i);
        if(ret != 0){ 
            fprintf(stderr, "pthread_create error %s\n", strerror(ret));
        }
    }   

    sleep(6);
    printf("this is main pid is %d pid is %lu\n", getpid(), pthread_self());

    return 0;
}

总结:

  • return :返回到调用者那里去
  • exit:退出当前进程
  • pthread_exit: 退出当前线程

线程回收

作用: 等待线程结束(此函数会阻塞),并回收线程资源与退出状态,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。

函数原型: int pthread_join(pthread_t thread, void **retval);

参数:

  • thread:要回收的线程号
  • retval:二级指针,用来存储线程退出状态的指针的地址。

返回值:

  • 成功:返回 0
  • 失败:返回 erron

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

struct stu{
    char name[256];
    int var;
};

void *tun(void *arg){
    struct stu *ttr;
    ttr = malloc(sizeof(ttr));

    ttr->var = 200;
    strcpy(ttr->name, "I am lihua\n");

    return (void *)ttr;
}


int main(int argc, char *argv[]){
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, tun, NULL);
    if(ret != 0){ 
        fprintf(stderr, "pthread_create error %s\n", strerror(ret));
    }   
    struct stu *sret;
    // 等待线程号为 tid 的线程,如果此线程结束就回收其资源
    // &sret保存线程退出的返回值
    ret = pthread_join(tid, (void **)&sret);
    if(ret != 0){ 
        fprintf(stderr, "pthread_join error %s\n", strerror(ret));
    }   
    printf("%s------%d\n", sret->name, sret->var);
    return 0;
}

循环创建多个线程,并且回收

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>


void *tun(void *arg){
    int i = (int)(long)arg;
    sleep(i);
    printf("I'm %dth thread tid is %lu\n",i+1, pthread_self());
    return (void *)pthread_self();
}


int main(int argc, char *argv[]){
    pthread_t tid[5];
    int i = 0;
    for(i = 0;i < 5;i++){
        int ret = pthread_create(&tid[i], NULL, tun, (void *)(long)i); //使用数组存储线程的地址
        if(ret != 0){ 
            fprintf(stderr, "pthread_create error %s\n", strerror(ret));
        }
    }   
    sleep(i);
    for(i = 0;i < 5;i++){
        int *tret;
        pthread_join(tid[i], (void **)&tret);
        printf("----pthread_join %dth thread id is %lu\n", i+1, (long)tret);
    }   

    return 0;
}

杀死线程

作用: 向线程发送一个取消请求

函数原型:int pthread_cancel(pthread_t thread);

参数:

  • thread:要发送请求的线程ID

返回值:

  • 成功:返回 0
  • 失败:返回 errno

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>


void *tun(void *arg){
    while(1){

        printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
        sleep(1);
    
    }   
    return NULL;
}


int main(int argc, char *argv[]){
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, tun, NULL);
    if(ret != 0){ 
        fprintf(stderr, "pthread_create error %s\n", strerror(ret));
    }   

    sleep(5);
    
    ret = pthread_cancel(tid);
    if(ret != 0){ 
        fprintf(stderr, "pthread_cancel error %s\n", strerror(ret));
    }   

    ret = pthread_join(tid, NULL);
    if(ret != 0){ 
        fprintf(stderr, "pthread_join error %s\n", strerror(ret));
    }   

    return 0;
}

注意: 线程的取消并不是实时的,而是具有一定的延时性。需要等待线程到达某个取消点才可以结束。

取消点 是线程检查是否被取消,并按请求进行动作的一个位置,通常是一些系统调用 creatopenpause...等。执行 man 7 pause可查看具备这些取消店的系统调用列表。

 可粗略认定为,一个系统调用(进入内核)即为一个取消点。如果线程中没有取消点,例如上述 demo,在 tun 函数中,执行了 sleepprintf 函数,都会进行系统调用进入内核,从而使得该线程到达取消点,线程被结束。如果将两行注释,按照上述程序,主线程会继续向下执行,阻塞在 pthread_join 函数处,等待线程到达取消点,完成 pthread_cancel 发送的请求,从而回收线程。如果线程中没有取消点,可以通过调用 pthread_testcancel()函数,自行设置一个取消点

 被取消的线程,退出值定义在 Linux 的 pthread 库中,常数 PTHREAD_CANCELED的值为 -1,可在头文件 pthread.h 中找到,因此通过 pthread_join 回收一个被 pthread_cancel 取消的线程时,得到的返回值为 -1

线程分离

作用: 使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的 是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。

函数原型: int pthread_detach(pthread_t thread);

参数:

  • thread:要分离的线程号

返回值:

  • 成功:返回 0
  • 失败:errno

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>


void *tun(void *arg){
    printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
    return NULL;
}


int main(int argc, char *argv[]){
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, tun, NULL);
    if(ret != 0){ 
        fprintf(stderr, "pthread_create error %s\n", strerror(ret));
    }   

    ret = pthread_detach(tid);// 线程分离,不阻塞,线程终止会自动清理,无需回收
    if(ret != 0){ 
        fprintf(stderr, "pthread_detach error %s\n", strerror(ret));
    }   
    //在线程分离后,回收时,该函数直接返回错误,调用失败
    ret = pthread_join(tid, NULL);
    if(ret != 0){ 
        fprintf(stderr, "pthread_create error %s\n", strerror(ret));
    }   

    printf("this is thread pid is %d tid is %lu\n",getpid(), pthread_self());
    pthread_exit(0);
}

线程属性

 Linux 下线程的属性是可以根据实际项目需要进行设置的,之前在创建线程的时候通常第二个参数 attr 通常传入 NULL,代表使用线程的默认属性,默认属性已经可以解决大多数开发时的问题,如果对程序的性能提出更高的需求,那么需要设置线程属性,比如设置线程的大小来降低内存的使用,增加最大线程的个数等

线程属性初始化

应先初始化线程属性,在创建线程

  • 初始化线程属性
    • int pthread_attr_init(pthread_attr_t *attr);
    • 成功:0
    • 失败:errno
  • 销毁线程属性所占用的资源
    • int pthread_attr_destroy(pthread_attr_t *attr);
    • 同上

线程的分离状态

 线程的分离状态决定一个线程以什么样的方式终止自己。

  • 非分离状态: 线程的默认属性时非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当调用 pthread_join()函数成功返回时,创建的线程才算终止,才能释放自己的系统资源(PCB)。

  • 分离状态: 分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源,应该根据自己的需要,选择适当的分离态

线程分离态的函数:

  • 设置线程属性:分离 or 不分离

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  • 获取线程属性:

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

参数:

  • attr:初始化的线程属性
  • detachstate:取值如下
    • PTHREAD_CREATE_DETACHED(分离线程)
    • PTHREAD_CREATE_JOINABLE(非分离线程)

 这里需要注意的一点,

demo

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>


void *tun(void *arg){
    return NULL;
}


int main(int argc, char *argv[]){
    pthread_t tid;
    pthread_attr_t attr;
    int ret = pthread_attr_init(&attr); //初始化线程属性
    if(ret  != 0){ 
        fprintf(stderr, "pthread_attr_init error %s\n", strerror(ret));
    }   

    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置线程属性为分离态
    if(ret != 0){ 
        fprintf(stderr, "pthread_attr_setdetachstate error %s\n", strerror(ret));
    }   
    
    ret = pthread_create(&tid, &attr,tun, NULL);//创建线程,直接为分离态
    if(ret != 0){ 
        fprintf(stderr, "pthread_create error %s\n", strerror(ret));
    }   
    //根据上文,当回收一个已经分离出去的线程,该函数会立即报错返回
    //用来检验创建分离线程时候成功
    ret = pthread_join(tid, NULL);
    if(ret != 0){ 
        fprintf(stderr, "pthread_join error %s\n", strerror(ret));
    }   

    pthread_attr_destroy(&attr); //销毁资源
    pthread_exit(0);
}

线程与进程函数控制原语对比

功能线程进程
创建 线/进 程pthread_create()fork() / vfork()
获取 线/进 程号pthread_self()getpid()
退出 线/进 程pthread_exit()exit() / _exit()
回收 线/进 程pthread_join()wait() / waitpid()
结束 线/进 程pthread_cancel()kill()
分离 线/进 程pthread_detach()

线程使用注意事项

  1. 主线程退出其他线程不退出,主线程应调用 pthread_exit 而非 return
  2. 避免产生僵尸线程
  3. 被 join 线程可能在 join 函数返回前就释放完了自己的所有内存资源,所以不应当返回被回收线程栈中的值
  4. malloc 与 mmap 申请的内存可以被其他线程释放
  5. 应避免在多线程模型中调用 fork ,创建的子进程中,只有调用 fork 的线程存在,其它线程在子进程中均被 pthread_exit
  6. 信号的复杂语义很难和多线程共存,应尽量避免在多线程中引入信号机制