1 线程概念

1.1 进程到线程

多进程之间互相沟通比较麻烦,比如内存共享、描述符共享、互斥与同步。同一个进程中的多线程使用的资源是共享的,比如内存,文件描述符等等。

1.2 线程资源

线程共享资源:

可执行程序的代码,程序的全局内存,堆内存,栈,文件描述符。

线程独有资源:

线程 ID,线程自己的一套寄存器值,线程运行栈,调度优先级和策略,信号屏蔽字,errno 变量,线程私有数据。

1.3 进程线程区别

进程被创建出来,自动的就包含了一个主线程,进程中真正的执行体是线程。进程是一种空间上的概念,为所有这些执行体(线程)提供必要的资源(内存、文件描述符、代码等)。线程是时间上的概念,它是抽象的、假想的、动态的指令执行过程。把进程理解成工厂以及工厂里的各种设备和资源,而线程就是工厂里一个个干活的工人。

2 线程创建与终止

2.1 线程创建

typedef void *(*start_routine) (void *);

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, start_routine th_fn, void *arg);
  • thread: 传出参数,存储线程 id。函数pthread_self可以获取自己的线程 id 号。

  • attr: 传入参数,线程属性。NULL表示默认属性

  • th_fn: 线程入口函数。

  • arg: 线程入口函数th_fn的参数。

  • 返回 0 表示成功。pthread_create 函数执行错误不会设置 errno 变量。

2.2 线程终止

包括主动终止和被动终止。

1、主动终止

  • 线程过程函数执行 return 正常返回,返回值是线程的退出码

  • 线种过程函数执行 pthread_exit 函数退出,其参数是线程退出码

2、被动终止

  • 在其它线程中调用 thread_cancel 函数

  • 任意线程调用 exit、_Exit 或者 _exit 导致整个进程终止

void pthread_exit(void *rval_ptr);

参数rval_ptr是线程退出码。通过函数 pthread_join 可以获取退出码。该函数类似于waitpid,回收线程资源。

int pthread_join(pthread_t tid, void **rval_ptr);

pthread_join 会阻塞,直到指定线程 tid 返回(return)、执行 pthread_exit 或者被其它线程取消(pthread_cancel)。rval_ptr用来接收退出码。

int pthread_cancel(pthread_t tid);

相当于在线程 tid 中调用 pthread_exit((void*)PTHREAD_CANCEL) 函数。线程退出码都是 PTHREAD_CANCELED

实验:线程主动退出

// threadexit.c
#include <unistd.h>                                                                                                   
#include <pthread.h>                                                                                                  
#include <stdio.h>                                                                                                    
#include <stdlib.h>                                                                                                   
#include <string.h>                                                                                                   

#define ERR(name,err) do{printf("%s:%s\n",#name,strerror(err));exit(-1);}while(0);                                    

void* th_fn1(void* arg) {
    puts("thread 1 returning");
    return (void*)10;
}

void* th_fn2(void* arg) {
    puts("thread 2 exiting");
    pthread_exit((void*)20);
    puts("thread 2 exited");
}

int main() {
    pthread_t tid1, tid2;
    int err;
    void* ret;

    err = pthread_create(&tid1, NULL, th_fn1, NULL);
    if (err != 0) ERR(pthread_create1, err);

    err = pthread_create(&tid2, NULL, th_fn2, NULL);
    if (err != 0) ERR(pthread_create2, err);

    sleep(2);

    // 主线程阻塞直到线程 tid1 退出                                                                                                                  
    err = pthread_join(tid1, &ret);
    if (err != 0) ERR(pthread_join1, err);
    printf("thread 1 exit code %d\n", (int)ret);

    // 主线程阻塞直到线程 tid2 退出                                                                                                                     
    err = pthread_join(tid2, &ret);
    if (err != 0) ERR(pthread_join2, err);
    printf("thread 2 exit code %d\n", (int)ret);

    return 0;
}

编译运行

$ gcc threadexit.c -o threadexit -lpthread
$ ./threadexit
thread 1 returning
thread 2 exiting
thread 1 exit code 10
thread 2 exit code 20

实验:线程被动退出

// threadcancel.c
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERR(name,err) do{printf("%s:%s\n",#name,strerror(err));exit(-1);}while(0);

void* th_fn1(void* arg) {
    while (1) {
        puts("thread 1 running");
        sleep(1);
    }
    return (void*)10;
}

void* th_fn2(void* arg) {
    while (1) {
        puts("thread 2 running");
        sleep(1);
    }
    pthread_exit((void*)20);
}

int main() {
    pthread_t tid1, tid2;
    int err;
    void* ret;

    err = pthread_create(&tid1, NULL, th_fn1, NULL);
    if (err != 0) ERR(pthread_create1, err);

    err = pthread_create(&tid2, NULL, th_fn2, NULL);
    if (err != 0) ERR(pthread_create2, err);

    sleep(5);

    // 通知 tid1 和 tid2 退出。
    pthread_cancel(tid1);
    pthread_cancel(tid2);

    err = pthread_join(tid1, &ret);

    // 线程退出码都是 PTHREAD_CANCELED
    printf("PTHREAD_CANCELED = %d\n", (int)PTHREAD_CANCELED);
    if (err != 0) ERR(pthread_join1, err);
    printf("thread 1 exit code %d\n", (int)ret);

    err = pthread_join(tid2, &ret);
    if (err != 0) ERR(pthread_join2, err);
    printf("thread 2 exit code %d\n", (int)ret);

    return 0;
}

编译运行

$ gcc threadcancel.c -o threadcancel -lpthread
$ ./threadcancel 
thread 1 running
thread 2 running
thread 1 running
thread 2 running
thread 1 running
thread 2 running
thread 2 running
thread 1 running
thread 1 running
thread 2 running
PTHREAD_CANCELED = -1
thread 1 exit code -1
thread 2 exit code -1

3 线程清理函数

希望线程退出时自动执行某些函数

void pthread_cleanup_push(void (*rtn)(void*), void *arg);

void pthread_cleanup_pop(int execute);

push 和 pop 必须成对出现,arg为函数rtn的参数。

有三种情况线程清理函数会被调用:

  1. 线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消

  2. 线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止

  3. 线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0

如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,不会执行清理函数。

// clean.c
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERR(name,err) do{printf("%s: %s",#name,strerror(err));exit(-1);}while(0);

int excute;

void cleanup(void* arg) {
    printf("cleanup: %s\n", (char*)arg);
}

void* th_fn1(void* arg) {
    puts("thread 1 starting");
    pthread_cleanup_push(cleanup, "线程 1 清理者 1 号");
    pthread_cleanup_push(cleanup, "线程 1 清理者 2 号");

    if (arg) {
        printf("线程 1 提前退出\n");
        return (void*)1;
    }

    pthread_cleanup_pop(excute);
    pthread_cleanup_pop(excute);
    printf("线程 1 正常退出\n");
    return (void*)10;
}

void* th_fn2(void* arg) {
    puts("thread 2 starting");
    pthread_cleanup_push(cleanup, "线程 2 清理者 1 号");
    pthread_cleanup_push(cleanup, "线程 2 清理者 2 号");

    if (arg) {
        printf("线程 2 提前退出\n");
        pthread_exit((void*)2);
    }

    pthread_cleanup_pop(excute);
    pthread_cleanup_pop(excute);

    printf("线程 2 正常退出\n");
    pthread_exit((void*)20);
}

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

    if (argc < 3) {
        printf("Usage: %s <arg 0|1> <excute 0|1>\n", argv[0]);
        return -1;
    }

    pthread_t tid1, tid2;
    int err;
    void* ret;
    void* arg = NULL;
    excute = 0;


    arg = (void*)atoi(argv[1]);
    excute = atoi(argv[2]);

    err = pthread_create(&tid1, NULL, th_fn1, arg);
    if (err != 0) ERR(pthread_create1, err);

    err = pthread_create(&tid2, NULL, th_fn2, arg);
    if (err != 0) ERR(pthread_create2, err);


    err = pthread_join(tid1, &ret);
    if (err != 0) ERR(pthread_join1, err);
    printf("thread 1 exit code %d\n", (int)ret);

    err = pthread_join(tid2, &ret);
    if (err != 0) ERR(pthread_join2, err);
    printf("thread 2 exit code %d\n", (int)ret);

    return 0;
}
  • 编译
$ gcc clean.c -o clean -lpthread
  • 运行

Linux系统编程(五)--线程基础_线程创建

可以看到:

  • 当 clean 程序中的线程正常返回时,只有 pthread_cleanup_pop 的参数非 0 时,才会正常执行清理函数。
  • 当 clean 程序中的线程在执行 pthread_cleanup_pop 前时,使用 pthread_exit 退出时,清理函数才会被执行,和 pthread_cleanup_pop 的参数没有关系。而使用 return 返回的线程 1 并不会执行清理函数。
  • 清理函数的执行顺序,是按照注册时候相反的顺序执行的。