素槿
Published on 2025-08-22 / 3 Visits
0

C语言多线程示例

0. 简介

当程序的性能到达瓶颈时,常用的解决方法是使用多线程,将任务或数据分割,使用不同的线程分别来处理,充分利用处理器的多核特性。

pthreadPOSIX thread)是 Linux提供的一套兼容 POSIX接口的线程库,是常用的线程库。

1. 线程创建

线程创建接口:

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

pthread_createpthread库的线程创建接口,使用这个接口需要包含 pthread.h头文件。线程创建成功时返回0,失败时返回相应错误码。

各个参数的含义如下:

  • thread 创建的线程的线程ID,用来唯一区别一个线程。
  • attr 线程属性
  • start_routine 线程执行函数
  • arg 要传递的参数

示例:

    const size_t threads = 10;
    pthread_t thread_arr[threads];
    memset(thread_arr, 0, threads);
    int retval = 0;
    for (size_t i = 0; i < threads; i++) {
        retval = pthread_create(&(thread_arr[i]), NULL, thread_handle, &i);
        if (retval) printf("thread_arr i=%ld create failed.\n", i);
        usleep(5000);
    }

这里我们创建10个线程,并使用一个数组 thread_arr储存创建的好的线程的ID,用于后续的线程合并。

pthread_create的第二个参数一般情况下设置为 NULL,thread_handle就是要执行的线程函数,最后一个参数中传入 i作为参数,若要传入多个参数可以使用结构体。

休眠5000微秒是为了完成线程中参数的复制,不然如果在参数完成复制前参数被改变,将会导致传入的参数错误。

2. 线程函数

线程函数的函数签名需为 void* func(void* arg),即参数和返回值的类型都是void指针。

void *thread_handle(void *arg)
{
    int i          = *(int *)arg;
    pthread_t *pid = (pthread_t *)malloc(sizeof(pthread_t));
    *pid           = pthread_self();
    printf("run thread id=%ld, i=%d\n", *pid, i);
    pthread_exit(pid);
}

在线程函数中,我们先复制了传入的参数;因为参数是指针,为了防止参数被改变,应先在参数在main中被改变前拷贝一份到当前线程。

函数中分配了一份内存来存放线程的ID。因为要返回的是一个指针,如果返回的是一个指向栈对象的指针,线程退出后临时对象会被释放,当我们获取时,获取到的就是一个错误或不存在的值。当然也可以使用全局变量传递返回值。

在这里我们使用 pthread_exit来退出线程并返回相应的值。pthread_exit的定义如下:

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

除了使用上述函数来退出外,还可以使用 return语句,或 exitpthread_cancel

3. 线程合并

    void *thret;
    for (int i = 0; i < threads; i++) {
        if (thread_arr[i] > 0) {
            pthread_join(thread_arr[i], &thret);
            printf("thread_arr[%d] return %ld.\n", i, *(pthread_t *)thret);
            free((pthread_t *)thret);
        }
    }

我们使用 pthread_join接口等待线程执行完毕,并获取返回值。函数的定义如下:

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

第一个参数为要等待的线程ID,第二个参数为线程的返回值。还要注意释放在线程中分配的用来储存返回值的内存。

4. 完整示例

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

void *thread_handle(void *arg)
{
    int i = *(int*)arg;
    pthread_t* pid = malloc(sizeof(pthread_t));
    *pid = pthread_self();  
    printf("run thread id=%ld, i=%d\n", *pid, i);
    pthread_exit(pid);

}

int main(int argc, char const *argv[])
{
    const size_t threads = 10;
    pthread_t thread_arr[threads];
    memset(thread_arr, 0, threads);
    int retval = 0;
    for (size_t i = 0; i < threads; i++){
        retval = pthread_create(&(thread_arr[i]), NULL, thread_handle, &i);
        if (retval)
            printf("thread_arr i=%ld create failed.\n", i);
        usleep(5000);
    }

    void* thret;
    for (int i = 0; i < threads; i++){
        if (thread_arr[i] > 0){
            pthread_join(thread_arr[i], &thret);
            printf("thread_arr[%d] return %ld.\n", i, *(pthread_t*)thret);
            free((pthread_t*)thret);
        }
    }
    printf("main exit.\n");
    return 0;
}

编译执行:

$ gcc pthread2.c -lpthread
$ ./a.out 
run thread id=140000393443072, i=0
run thread id=140000384939776, i=1
run thread id=140000376547072, i=2
run thread id=140000297481984, i=3
run thread id=140000289089280, i=4
run thread id=140000280696576, i=5
run thread id=140000272303872, i=6
run thread id=140000263911168, i=7
run thread id=140000255518464, i=8
run thread id=140000247125760, i=9
thread_arr[0] return 140000393443072.
thread_arr[1] return 140000384939776.
thread_arr[2] return 140000376547072.
thread_arr[3] return 140000297481984.
thread_arr[4] return 140000289089280.
thread_arr[5] return 140000280696576.
thread_arr[6] return 140000272303872.
thread_arr[7] return 140000263911168.
thread_arr[8] return 140000255518464.
thread_arr[9] return 140000247125760.
main exit.

编译的时候注意需要链接 pthread库。否则不能使用相关的接口。可以看到,线程的ID和传入的参数都被线程正确打印出来了,返回的线程ID也获取并打印。

5. 后话

关于多线程有很多的内容,比如线程分离,线程属性,线程同步等。这里只是一个简单的多线程示例。

实力有限,代码仅供参考,如有错误或不合适的地方欢迎提出讨论。