Bootstrap

【C语言多线程】pthread库的简单使用

基本函数

线程创建 pthread_create

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
//返回值:成功返回0,失败返回错误编号

pthread_t *thread:线程ID,由函数pthread_self()获取,类似获取进程pid使用getpid()函数;

const pthread_attr_t *attr:用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程;

void *(*start_routine) (void *):线程中执行函数。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg

void *arg:执行函数中中参数。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构体中,然后把这个结构的地址作为arg参数传入

线程退出 pthread_exit

单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:

1)线程只是从启动例程中返回,返回值是线程的退出码。

2)线程可以被同一进程中的其他线程取消。

3)线程调用pthread_exit:

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

rval_ptr:是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

线程等待 pthread_join

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号

调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消
如果例程只是从它的启动例程返回,rval_ptr将包含返回码。
如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。
①可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
②如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。

简单使用

我们先编写一个简单的程序感受一下多线程

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

void *thread_func(void *arg)
{
    int i = 0;
    while (i < 10) {
        printf("New thread arg: %d, %d\n", *(int *)arg, i);
        i++;
    }
}

int main()
{
    pthread_t thread_id;
    int arg = 123;
    int ret;

    ret = pthread_create(&thread_id, NULL, thread_func, (void *)&arg);
    if (ret != 0) {
        printf("Failed to create new thread");
        exit(EXIT_FAILURE);
    }

    printf("Main thread: New thread created with ID %ld\n", thread_id);

    pthread_join(thread_id, NULL);
}

输出:

Main thread: New thread created with ID 1
New thread arg: 123, 0
New thread arg: 123, 1
New thread arg: 123, 2
New thread arg: 123, 3
New thread arg: 123, 4
New thread arg: 123, 5
New thread arg: 123, 6
New thread arg: 123, 7
New thread arg: 123, 8
New thread arg: 123, 9

首先我们在main中创建了一个thread_func线程,并传入了arg参数,main函数使用pthread_join方法等待thread_func线程的循环结束后再退出。

注意:如果不在main中写pthread_join,则main函数不会等待thread_func执行结束而结束,而是会继续往下运行直到结束后直接退出,不管thread_func是否运行结束。

注意到:如果我们多次调用pthread_create会发生什么呢?

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

void *thread_func(void *arg)
{
    int i = 0;
    while (i < 10) {
        printf("New thread arg: %d, %d\n", *(int *)arg, i);
        i++;
    }
}

int main()
{
    pthread_t thread_id;
    int arg = 123;
    int ret;

    ret = pthread_create(&thread_id, NULL, thread_func, (void *)&arg);
    if (ret != 0) {
        printf("Failed to create new thread");
        exit(EXIT_FAILURE);
    }
    ret = pthread_create(&thread_id, NULL, thread_func, (void *)&arg);
    if (ret != 0) {
        printf("Failed to create new thread");
        exit(EXIT_FAILURE);
    }

    printf("Main thread: New thread created with ID %ld\n", thread_id);

    pthread_join(thread_id, NULL);
}

在这里我们多次调用pthread_create,输出如下

Main thread: New thread created with ID 2
New thread arg: 123, 0
New thread arg: 123, 0
New thread arg: 123, 1
New thread arg: 123, 2
New thread arg: 123, 3
New thread arg: 123, 1
New thread arg: 123, 2
New thread arg: 123, 4
New thread arg: 123, 3
New thread arg: 123, 5
New thread arg: 123, 4
New thread arg: 123, 5
New thread arg: 123, 6
New thread arg: 123, 7
New thread arg: 123, 6
New thread arg: 123, 7
New thread arg: 123, 8
New thread arg: 123, 9
New thread arg: 123, 8
New thread arg: 123, 9

发现其实创建了两个线程,而且多次运行这个程序后会发现每次的输出都不一样。

那如果我们只需要存在一个线程呢?
最简单的方法就是设置一个标志变量,记录线程是否启动

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

#define TRUE 1
#define FALSE 0
#define SUCCESS 0
#define FAIL -1

static int isStart = FALSE;
pthread_t thread_id;

void *thread_func(void *arg)
{
    int i = 0;
    while (i < 10) {
        printf("New thread arg: %d, %d\n", *(int *)arg, i);
        usleep(100000);
        i++;
    }
}

int Init(void *arg){
    if (isStart){
        printf("Failed to create new thread : thread is started\n");
        return FAIL;
    }else {
        int ret;
        isStart = TRUE;
        ret = pthread_create(&thread_id, NULL, thread_func, (void *)arg);
        if (ret != SUCCESS) {
            printf("Failed to create new thread");
            return FAIL;
        }
        return SUCCESS;
    }
}

int main()
{
    int arg = 123;
    int ret;

    ret = Init(&arg);
    if (ret == FAIL){
        printf("init fail!\n");
    }
    ret = Init(&arg);
    if (ret == FAIL){
        printf("init fail!\n");
    }

    printf("Main thread: New thread created with ID %ld\n", thread_id);

    pthread_join(thread_id, NULL);
}

输出:

Failed to create new thread : thread is started
init fail!
Main thread: New thread created with ID 1
New thread arg: 123, 0
New thread arg: 123, 1
New thread arg: 123, 2
New thread arg: 123, 3
New thread arg: 123, 4
New thread arg: 123, 5
New thread arg: 123, 6
New thread arg: 123, 7
New thread arg: 123, 8
New thread arg: 123, 9

注意!!!
此方法只能在单个进程的场景下使用,如果存在多个进程都会调用这里的接口,则仍然会在每个进程中存在这样的一个线程,可能导致出现问题。
笔者目前想到的规避方法是:使用内核线程,在内核中通过全局变量的方法控制只存在一个内核线程

;