C语言多线程编程:使用pthread库创建与管理线程

8 次阅读

C语言多线程编程是现代系统开发中的重要技术,通过合理利用多核CPU资源,可以显著提升程序执行效率。Linux环境下,pthread库(POSIX Threads)是实现C语言多线程开发的标准方案,被广泛应用于服务器程序、网络通信、嵌入式系统以及高性能计算领域。

相比传统单线程程序,多线程能够让多个任务并行执行。例如,一个网络服务器可以使用不同线程处理多个客户端请求,一个图像处理程序可以将计算任务拆分到多个线程中同时运行。掌握pthread线程创建、管理以及同步机制,是进行C语言高级开发的重要基础。

pthread库简介

pthread全称为POSIX Threads,是POSIX标准定义的一套线程操作接口。它提供了一系列函数,用于创建线程、终止线程、线程同步以及线程间通信。

Linux系统默认支持pthread库,但在编译程序时需要显式链接线程库:

Bash
gcc main.c -o main -pthread

-pthread参数不仅会链接pthread库,还会启用与线程相关的编译选项,因此推荐使用该方式进行编译。

pthread常用功能包括:

  • 创建线程

  • 等待线程结束

  • 分离线程

  • 线程退出

  • 线程互斥锁

  • 条件变量同步

  • 读写锁控制

pthread线程基本结构

在C语言中,每个线程本质上都是一个独立执行的函数。创建线程时,需要指定线程入口函数,线程启动后会自动执行该函数。

线程函数通常具有以下形式:

C
void *thread_function(void *arg)
{
    // 线程执行代码

    return NULL;
}

函数返回类型必须是void *,参数也是void *,这样可以传递任意类型的数据。

例如:

C
#include 
#include 

void *task(void *arg)
{
    printf("子线程正在运行
");

    return NULL;
}

int main()
{
    pthread_t thread;

    pthread_create(&thread, NULL, task, NULL);

    pthread_join(thread, NULL);

    printf("主线程结束
");

    return 0;
}

程序运行过程:

  1. 主线程调用pthread_create()创建新线程。

  2. 新线程执行task()函数。

  3. 主线程调用pthread_join()等待子线程结束。

  4. 所有线程完成后程序退出。

使用pthread_create创建线程

pthread_create()是pthread库中最核心的函数,用于创建新的执行线程。

函数原型:

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

参数说明:

thread

用于保存新创建线程的线程ID:

C
pthread_t tid;

后续可以通过该ID管理线程。

attr

线程属性参数,一般情况下传入NULL:

C
pthread_create(&tid, NULL, function, NULL);

如果需要设置线程栈大小、调度策略等,可以通过线程属性对象配置。

start_routine

线程入口函数:

C
void *worker(void *arg)

线程启动后会执行该函数。

arg

传递给线程函数的参数,可以是任意类型。

例如传递整数:

C
int num = 100;

pthread_create(&tid, NULL, worker, &num);

在线程函数中:

C
void *worker(void *arg)
{
    int value = *(int *)arg;

    printf("%d
", value);

    return NULL;
}

pthread_join等待线程结束

线程创建后,默认情况下主线程不会等待子线程执行完成。

如果主线程提前结束,整个进程也会退出,其他线程可能无法完成任务。

pthread_join()用于等待指定线程结束。

示例:

C
pthread_join(thread, NULL);

函数原型:

C
int pthread_join(
    pthread_t thread,
    void **retval
);

第一个参数是目标线程ID。

第二个参数用于获取线程返回值。

例如:

C
void *worker(void *arg)
{
    int *result = malloc(sizeof(int));

    *result = 100;

    return result;
}

int main()
{
    pthread_t tid;
    void *ret;

    pthread_create(&tid, NULL, worker, NULL);

    pthread_join(tid, &ret);

    printf("结果:%d
", *(int *)ret);

    free(ret);

    return 0;
}

pthread_exit退出线程

线程可以通过pthread_exit()主动结束。

示例:

C
void *worker(void *arg)
{
    printf("线程执行完成
");

    pthread_exit(NULL);
}

与普通return相比:

  • pthread_exit()只结束当前线程。

  • exit()会结束整个进程。

因此在线程环境中不要随意调用exit()。

线程分离pthread_detach

默认情况下,线程结束后需要其他线程调用pthread_join()回收资源。

如果不需要等待线程,可以使用分离模式:

C
pthread_detach(thread);

分离后的线程:

  • 结束后自动释放资源。

  • 不需要调用pthread_join。

  • 不能再次join。

常用于后台任务:

C
void *background_task(void *arg)
{
    while(1)
    {
        printf("后台任务运行
");
        sleep(1);
    }

    return NULL;
}

创建后:

C
pthread_create(&tid,NULL,background_task,NULL);

pthread_detach(tid);

多线程中的数据共享问题

线程最大的特点是共享进程地址空间。

多个线程可以访问:

  • 全局变量

  • 静态变量

  • 堆内存

例如:

C
int count = 0;

void *increase(void *arg)
{
    for(int i=0;i<10000;i++)
    {
        count++;
    }

    return NULL;
}

如果多个线程同时执行:

C
count++;

可能出现数据错误。

原因是count++并不是一个原子操作,它实际上包含:

  1. 读取变量

  2. 加一计算

  3. 写回变量

多个线程交叉执行时会导致竞争条件。

pthread_mutex互斥锁解决线程竞争

pthread提供互斥锁(mutex)保护共享资源。

定义互斥锁:

C
pthread_mutex_t mutex;

初始化:

C
pthread_mutex_init(&mutex,NULL);

加锁:

C
pthread_mutex_lock(&mutex);

访问共享数据:

C
count++;

解锁:

C
pthread_mutex_unlock(&mutex);

完整示例:

C
#include 
#include 

int count = 0;

pthread_mutex_t mutex;

void *worker(void *arg)
{
    for(int i=0;i<10000;i++)
    {
        pthread_mutex_lock(&mutex);

        count++;

        pthread_mutex_unlock(&mutex);
    }

    return NULL;
}

int main()
{
    pthread_t t1,t2;

    pthread_mutex_init(&mutex,NULL);

    pthread_create(&t1,NULL,worker,NULL);
    pthread_create(&t2,NULL,worker,NULL);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    printf("count=%d
",count);

    pthread_mutex_destroy(&mutex);

    return 0;
}

通过互斥锁,可以保证同一时间只有一个线程修改共享变量。

pthread条件变量实现线程同步

互斥锁只能控制访问权限,无法实现线程等待某个条件。

例如:

  • 消费者等待生产者生成数据。

  • 工作线程等待任务队列。

此时可以使用条件变量。

定义:

C
pthread_cond_t cond;

初始化:

C
pthread_cond_init(&cond,NULL);

等待:

C
pthread_cond_wait(&cond,&mutex);

通知:

C
pthread_cond_signal(&cond);

广播:

C
pthread_cond_broadcast(&cond);

典型应用就是生产者消费者模型。

pthread读写锁优化并发访问

如果程序中读操作远多于写操作,可以使用读写锁。

读写锁允许:

  • 多个线程同时读取。

  • 写操作独占资源。

定义:

C
pthread_rwlock_t rwlock;

读锁:

C
pthread_rwlock_rdlock(&rwlock);

写锁:

C
pthread_rwlock_wrlock(&rwlock);

释放:

C
pthread_rwlock_unlock(&rwlock);

数据库缓存、配置文件读取等场景非常适合使用读写锁。

多线程编程常见问题

线程死锁

死锁通常发生在多个线程互相等待资源。

例如:

线程A:

锁住资源1
等待资源2

线程B:

锁住资源2
等待资源1

两个线程都会永久等待。

避免方法:

  • 保持锁顺序一致。

  • 减少锁嵌套。

  • 避免长时间持有锁。

线程资源泄漏

创建大量线程后,如果没有正确回收,会造成系统资源浪费。

解决方式:

  • 使用pthread_join回收。

  • 使用pthread_detach自动释放。

共享变量没有保护

所有线程共享的数据都应该考虑同步问题。

常见解决方案:

  • mutex互斥锁。

  • atomic原子操作。

  • 线程局部存储。

pthread多线程编程实践建议

实际开发中,合理设计线程模型比简单创建多个线程更加重要。

常见优化方式包括:

使用线程池

频繁创建和销毁线程成本较高。

线程池可以:

  • 提前创建固定数量线程。

  • 复用线程资源。

  • 提升服务器性能。

控制线程数量

线程数量并不是越多越好。

过多线程会导致:

  • 上下文切换增加。

  • 内存消耗提高。

  • CPU调度压力增大。

通常需要根据任务类型和CPU核心数量调整。

减少锁竞争

锁会降低并发性能。

优化方法:

  • 缩小锁范围。

  • 使用读写锁。

  • 减少共享数据。

pthread与C语言多线程开发总结

pthread库为C语言提供了一套完整的多线程开发接口,从线程创建、线程退出,到线程同步和资源管理,都能够满足大多数系统级开发需求。

掌握pthread_create()创建线程、pthread_join()等待线程、pthread_mutex保护共享资源以及条件变量同步,是进行Linux多线程编程的基础。

在实际项目中,多线程并不是简单增加线程数量,而是需要结合任务特点设计合理的并发模型。通过正确使用pthread库,可以充分发挥多核CPU性能,提高程序响应速度和处理能力。