一、线程的基本使用

正常情况下,一个进程只有一个一线程在运行。有了多线程之后,就可以使得程序在同一时间可以做多间不同的事情。

这样就可以利用起来很多被浪费掉的时间,例如执行阻塞任务,磁盘IO等等,这些等待的时间都可以被用来做其他的事情,这也就是多线程的用武之地。

1.1 线程标识

线程通过线程ID来标识,在linux环境中被定义为pthread_t类型。在调试中,我们可以把它当作一个整形变量打印,但是实际上在严谨的场合不能这么干,它并不等于整形。

判断两个线程id是否相等要使用linux提供的函数接口:

#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);

对线程而言,可以通过pthread_self()函数来获得当前线程的ID,例如打印出一个函数中主线程的id:

#include <stdio.h>
#include <pthread.h>

int main() {
    pthread_t tid;

    tid = pthread_self();

    printf("main thread: %08x\n", tid);

    return 0;
}

编译运行:

main thread: 0x66744700
注意,多线程程序编译时要加-lpthread选项

1.2 创建一个函数

创建一个线程的函数原型:

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

各个参数的含义:

  • thread:用来接收新创建的线程ID,该参数不能省略,不能写成NULL
  • attr:设置线程的属性,一般为NULL
  • start_routine:线程启动的函数,是一个函数指针,线程启动后将调用这个函数
  • arg:自定义参数,会作为线程函数的启动参数传入

以下是一个简单的创建线程的示例,创建一个线程并打印出它当前的进程id和线程id:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

typedef unsigned int uint;

void f(const char *s) {
    pid_t pid = getpid();
    pthread_t tid = pthread_self();
    // 打印出当前的进程id和线程id
    printf("%s pid is %d, tid is 0x%x\n", s, pid, (uint)tid);
}

void *thread_start(void *arg) {
    f((char*)arg);
    return 0;
}

int main(){
    int err;
    pthread_t tid;
    // 创建线程
    err = pthread_create(&tid, 0, thread_start, (void*)"new thread:");
    if (err != 0) {
        fprintf(stderr, "pthread_create error: %s", strerror(err));
        return 0;
    }
    printf("main thread: pid is %d, child thread is 0x%x.\n", (int)getpid(), (uint)tid);
    // 等待线程执行完毕
    sleep(1);
    return 0;
}

运行结果:

main thread: pid is 18267, child thread is 0xe33ba700.
new thread: pid is 18267, tid is 0xe33ba700

根据两个线程打印出来的pid可以看到:多线程实际上还是处于同一个进程的。

二、线程终止

2.1 pthread_exit()

pthread_exit()的作用就是退出当前线程,并设置退出的返回值。定义为:

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

retval参数表示线程的退出状态。

线程也可以使用return来返回,但是在线程定义了清理函数的时候,如果使用return返回,清理函数将不会被执行。

2.2 pthread_join()

上面代码的主函数中,最后面有一个sleep(1)来等待子线程退出,这么做实际上是不合理的,因为线程和进程一样,执行的时机是不确定的,1S之后线程可能还没有执行完。

如果要在主线程等待子线程执行完毕的话,可以通过pthread_join()函数来完成,其定义为:

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

参数含义:

  • thread:线程id
  • retval:线程的返回码

调用这个函数后,线程会一直阻塞,直到指定线程退出。如果调用时线程已经处于结束状态,那么函数会立即返回。

如果指定的线程被取消了,retval的值将会是PTHREAD_CANCELED。不关心线程返回值的话,可以直接把retval设为NULL。

注意,如果线程被分离了(执行了pthread_detach()),执行pthread_join()无效。

以下是一个调用pthread_exit()pthread_join()的示例:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

typedef unsigned int uint;

void *func1(void *arg) {
    pthread_t tid;
    tid = pthread_self();

    printf("this is func1(), tid = 0x%08x\n", tid);
    return (void *)111;
}

void *func2(void *arg) {
    pthread_t tid;
    tid = pthread_self();

    printf("this is func2(), tid = 0x%08x\n", tid);
    pthread_exit((void *)222);
}

// 创建一个线程,并打印出对应的信息
int pthread_create_ex(pthread_t *tid, void *(pf)(void*)) {
    int err;

    err = pthread_create(tid, 0, pf, NULL);
    if (err != 0) {
        fprintf(stderr, "pthread_create error: %s", strerror(err));
    } else {
        printf("main thread: child thread is 0x%x\n", tid);
    }

    return err;
}

// 回收一个线程,并打印相关信息
int pthread_join_ex(pthread_t tid) {
    int err, retval;

    err = pthread_join(tid, (void *)&retval);
    if (err != 0) {
        fprintf(stderr, "pthread_join error: %s", strerror(err));
    } else {
        printf("thread 0x%x has exit, code %d\n", tid, retval);
    }

    return err;
}

int main(){
    int err; 
    pthread_t tid1, tid2;

    err = pthread_create_ex(&tid1, func1);
    if (err != 0)
        return -1;

    err = pthread_create_ex(&tid2, func2);
    if (err != 0)
        return -1;

    err = pthread_join_ex(tid1); 
    if (err != 0)
        return -1;

    err = pthread_join_ex(tid2);
    if (err != 0)
        return -1;
    
    return 0;
}

这里的代码用两个函数作为不同的线程开始,分别通过returnpthread_exit来返回,运行结果:

可以看到,pthread_join()都能拿到返回值,那这两者是否有区别呢?下面将会继续探究。

2.2 pthread_cancel()

pthread_cancel()函数可以取消同一进程中的其他线程:

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

参数就是希望取消的线程id,这会使得被取消的线程返回PTHREAD_CANCELED

#include <stdio.h>
#include <pthread.h>

void *f1(void *arg) {
    int i = 0;
    while (i++ < 5) {
        printf("second %d\n", i);
        sleep(1);
    }
    pthread_exit((void *)99);
}

void *f2(void *arg) {
    pthread_t tid;

    tid = *(pthread_t *)arg;

    sleep(1);
    printf("pthread_cancel --> 0x%08x\n", tid);
    pthread_cancel(tid);
    pthread_exit((void *)0);
}

int main() {
    pthread_t tid1, tid2;
    pthread_t ret1, ret2;

    pthread_create(&tid1, NULL, f1, NULL);
    pthread_create(&tid2, NULL, f2, (void *)&tid1);

    pthread_join(tid1, (void *)&ret1);
    pthread_join(tid2, (void *)&ret2);

    printf("tid1: %08x, exit status: %d\n", tid1, ret1);
    printf("tid2: %08x, exit status: %d\n", tid2, ret2);
    printf("PTHREAD_CANCELED = %d\n", PTHREAD_CANCELED);

    return 0;
}

以上是一个简单的调用示例,产生两个线程,一个线程每隔一秒打印一次,另一个线程则在一秒后关闭该线程。

主线程中等待退出并打印返回码,结果如下所示:

被取消掉的线程91ef7700并没有返回其函数内定义的99,而是-1,也就是PTHREAD_CANCELED的值。

最后修改:2019 年 05 月 03 日
如果觉得我的文章对你有用,请随意赞赏