百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

C语言多线程编程指南

yund56 2025-04-01 16:57 7 浏览

多线程可以提升程序的并发能力,充分利用多核 CPU 的优势。但多线程也带来共享资源竞态、死锁、调试复杂等问题。本文将带你循序渐进地了解在 C 语言中如何正确、安全地使用多线程技术。


1. 多线程编程简介

在 C 语言中,多线程允许同一进程中的多个执行流(线程)并发运行。主要有以下几点注意:

  • 线程与进程的区别 线程是进程内的轻量级执行单元,共享同一进程的内存空间;而进程则拥有独立的地址空间。线程之间的通信非常高效,但需要额外同步来避免数据竞争。
  • 多线程的优势
    • 并发处理任务,提高 CPU 利用率。
    • 适合 I/O 密集型程序,通过一部分线程处理 I/O,另一部分线程继续计算。
  • 多线程的挑战
    • 同步与互斥问题:多个线程对共享数据进行读写可能导致数据不一致。
    • 死锁:资源分配不当可能造成线程相互等待。
    • 调试与测试难度增加。

2. 常见的多线程库与 API

在 C 语言中,多线程实现主要有以下几种方式:

  • POSIX 线程 (pthreads) 在 Unix/Linux 平台上广泛使用,提供了线程创建、管理和同步的 API。
  • C11 标准线程库 C11 在 中引入了标准化多线程支持,可在支持 C11 的编译器中使用,但因兼容性问题,目前应用较少。
  • GLib 线程 GLib 为跨平台提供了线程、互斥锁、条件变量等封装。如果你已经在使用 GLib,其线程支持会使跨平台编程更简单。
  • Windows 线程 (Win32 API) Windows 平台有自己的一套线程 API(如 CreateThread),但本文主要以 POSIX、C11 和 GLib 为例。

3. 使用 POSIX 线程(pthreads)

3.1 创建与结束线程

下面的示例展示如何使用 POSIX 线程创建多个线程,并用 pthread_join 等待线程结束:

 #include 
 #include 
 #include 
 
 #define THREAD_COUNT 5
 
 void* thread_func(void *arg) {
     int tid = *(int *)arg;
     printf("线程 %d 正在运行\n", tid);
     // 释放传递的数据
     free(arg);
     return NULL;
 }
 
 int main() {
     pthread_t threads[THREAD_COUNT];
     for (int i = 0; i < THREAD_COUNT; i++) {
         int *tid = malloc(sizeof(int));
         if (tid == NULL) {
             perror("内存分配失败");
             exit(EXIT_FAILURE);
         }
         *tid = i;
         if (pthread_create(&threads[i], NULL, thread_func, tid) != 0) {
             perror("无法创建线程");
             exit(EXIT_FAILURE);
         }
     }
     // 等待所有线程退出
     for (int i = 0; i < THREAD_COUNT; i++) {
         pthread_join(threads[i], NULL);
     }
     printf("所有线程均已结束\n");
     return 0;
 }

说明:

  • pthread_create 创建线程时需要传递一个指向线程函数的指针和一个参数。
  • 线程函数返回 void*,可用于传递退出状态。
  • 使用 pthread_join 等待线程结束,并回收线程资源(避免产生僵尸线程)。

3.2 线程分离

如果不需要等待线程结束,可以使用 pthread_detach 将线程设置为分离状态,从而在其退出时自动释放资源:

 // 创建线程后调用
 pthread_detach(thread_id);

4. 线程同步与互斥

当多个线程共享数据时,应使用同步机制来防止数据竞争。常见的同步原语包括互斥锁(mutex)、条件变量(condition variable)和信号量。

4.1 互斥锁

互斥锁保证在任一时刻只有一个线程能够访问共享数据。示例代码:

 #include 
 #include 
 
 int shared_counter = 0;
 pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 void* increment_func(void *arg) {
     for (int i = 0; i < 10000; i++) {
         pthread_mutex_lock(&counter_mutex);
         shared_counter++;
         pthread_mutex_unlock(&counter_mutex);
     }
     return NULL;
 }
 
 int main() {
     pthread_t t1, t2;
     pthread_create(&t1, NULL, increment_func, NULL);
     pthread_create(&t2, NULL, increment_func, NULL);
 
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
 
     printf("最终计数值: %d\n", shared_counter);
     pthread_mutex_destroy(&counter_mutex);
     return 0;
 }

4.2 条件变量

条件变量用于在线程间传递状态变化通知。下例中使用条件变量实现简单的生产者—消费者模型:

 #include 
 #include 
 
 int ready = 0;
 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
 void* consumer(void *arg) {
     pthread_mutex_lock(&mutex);
     while (!ready) {
         // 等待生产者发送信号,释放mutex后进入睡眠态
         pthread_cond_wait(&cond, &mutex);
     }
     printf("消费者收到信号,开始处理数据\n");
     pthread_mutex_unlock(&mutex);
     return NULL;
 }
 
 void* producer(void *arg) {
     // 模拟生产过程
     sleep(2);
     pthread_mutex_lock(&mutex);
     ready = 1;
     pthread_cond_signal(&cond);
     pthread_mutex_unlock(&mutex);
     return NULL;
 }
 
 int main() {
     pthread_t prod, cons;
     pthread_create(&cons, NULL, consumer, NULL);
     pthread_create(&prod, NULL, producer, NULL);
 
     pthread_join(prod, NULL);
     pthread_join(cons, NULL);
 
     pthread_mutex_destroy(&mutex);
     pthread_cond_destroy(&cond);
     return 0;
 }

说明:

  • pthread_cond_wait 会自动释放给定的 mutex,当条件满足或收到信号后重新获得 mutex。
  • 流程设计中务必防止“虚假唤醒”,通常使用 while 循环反复检测条件。

5. 使用 GLib 实现跨平台多线程编程

GLib 封装了跨平台线程(GThread)、互斥锁(GMutex)和条件变量(GCond)等 API。示例如下:

 #include 
 #include 
 
 gboolean flag = FALSE;
 GMutex mutex;
 GCond cond;
 
 gpointer thread_func(gpointer data) {
     g_mutex_lock(&mutex);
     // 等待 flag 变为 TRUE
     while (!flag) {
         g_cond_wait(&cond, &mutex);
     }
     g_mutex_unlock(&mutex);
     g_print("线程收到信号,开始工作\n");
     return NULL;
 }
 
 int main() {
     g_mutex_init(&mutex);
     g_cond_init(&cond);
 
     GThread *thread = g_thread_new("worker", thread_func, NULL);
 
     // 主线程模拟一些工作(例如 2 秒延时)
     g_usleep(2000000);
 
     g_mutex_lock(&mutex);
     flag = TRUE;
     g_cond_signal(&cond);
     g_mutex_unlock(&mutex);
 
     g_thread_join(thread);
 
     g_cond_clear(&cond);
     g_mutex_clear(&mutex);
 
     return 0;
 }

优点: GLib 的 API 在 Windows 与 Linux 平台均可运行,能帮助开发者避免平台差异问题。


6. C11 标准中的线程支持

C11 在 中定义了一套标准化线程 API,示例代码如下(注意:部分编译器可能需额外选项或不完全支持):

 #include 
 #include 
 
 int thread_func(void *arg) {
     printf("C11线程正在运行\n");
     return 0;
 }
 
 int main(void) {
     thrd_t thr;
     if (thrd_create(&thr, thread_func, NULL) == thrd_success) {
         thrd_join(thr, NULL);
     } else {
         fprintf(stderr, "线程创建失败\n");
     }
     return 0;
 }

虽然 C11 线程库的推广情况不如 POSIX 线程,但它为标准化多线程带来了一种可能。


7. 多线程编程中的最佳实践

7.1 设计阶段

  • 避免过度共享: 设计时尽量减少多个线程共享的数据,或采用消息传递模型。
  • 使用有限的锁: 尽量缩小锁的作用域,减少锁竞争。
  • 明确线程职责: 每个线程只集中处理单一任务,避免职责混乱。

7.2 编程阶段

  • 错误检查: 检查每个线程创建和同步函数的返回值。
  • 释放资源: 使用 pthread_join 或分离线程,确保所有动态资源正确释放。
  • 防止死锁: 保证连续获取多个资源时顺序一致,必要时可采用 try-lock 方式。

7.3 调试与测试

  • 利用专用工具(如 Valgrind 的 Helgrind、ThreadSanitizer)检测竞态条件。
  • 采用日志记录、断言等手段追踪线程执行流,帮助复现问题。

8. 调试与性能优化

多线程调试较单线程复杂,可考虑以下策略:

  • 日志与断言: 在关键代码部位添加日志输出,及时验证运行状态。
  • 动态分析工具: 使用 ThreadSanitizer 等工具检测数据竞争与同步缺陷。
  • 最佳化锁使用: 分析程序热点,尽量减少锁的粒度或考虑无锁数据结构。
  • 合理调度: 合理控制线程数目,太多线程可能因上下文切换过多反而降低性能。

此外,对于 I/O 密集型任务,可考虑用异步 I/O 模型;对于 CPU 密集型任务,则尽量使各线程相互独立,以充分利用多核资源。


9. 延伸阅读与探索:

  • 了解线程池(Thread Pool)设计,减少线程创建和销毁开销。
  • 探索无锁编程(Lock-Free Programming)和原子操作(atomic operation)技术,提高程序并发性。
  • 研究现代并发编程范式,如 Actor 模型、消息队列等。
  • 关注 C 语言新标准中对并发支持的改进,及时更新知识体系。平台优化线程模型,或者如何处理复杂的线程调试问题,我们可以继续深入探讨。

相关推荐

仍需打磨:首款Windows 10X模拟器上手

今天,微软发布了适用于Windows10X的首款模拟器,以便于开发人员初步了解适用于双屏设备的操作系统调整。微软希望在SurfaceNeo今年年底正式发售之前,让开发人员对应用程序进行优化。因此...

Windows10 编译OpenCV4.5源码

在OpenCV4.5+VisualStudio2017开发环境配置中,介绍了OpenCV4.5的下载和安装,待扩展内容OpenCV源码编译,在本文中做补充。研究源码无疑是学习OpenCV的一...

微软7年磨一剑,Windows 10X抢先上手体验

2月22日消息,微软在去年10月正式推出了Windows10X系统,该系统除了可用于传统的电脑外,同样适用于双屏设备或者折叠屏设备,拥有更好的触控操作体验。Windows10X在操作系统底层、命令...

Office重新设计了图标,你觉得如何?

微软重新设计了Office的应用图标,在接下来的几个月里,这些图标将从移动端和网页端开始陆续推广至各大平台。距离Office图标的最近一次更新还是在2013年,那时鲍尔默时代的产物,那时微软还在纠结是...

微软发布 Win10 Build 21376 内测版:重新设计默认用户界面字体

IT之家5月7日消息今年早些时候,微软意外地确认正在为Windows10进行UI改进,并在预览版中发现了相关的非活动代码。微软今天宣布向开发渠道中的内测用户发布Windows1...

前端开发需要了解常用7种JavaScript设计模式

作者|Deven译者|王强策划|小智转发链接:https://mp.weixin.qq.com/s/Lw4D7bfUSw_kPoJMD6W8gg前言JavaScript中的设计模式指的是...

「Qt入门第二篇」基础(二)编写Qt多窗口程序

导语程序要实现的功能是:程序开始出现一个对话框,按下按钮后便能进入主窗口,如果直接关闭这个对话框,便不能进入主窗口,整个程序也将退出。当进入主窗口后,我们按下按钮,会弹出一个对话框,无论如何关闭这个对...

在吴中 ,哪里有学网页设计的培训班?

网页设计介绍Web2.0标准布局之网页长期签约就业班(全日制)课程收费:7680元课程周期:5-6个月(45分钟/课)使用教材:《教师自编教材》考核发证:Adobe《网页设计师》培训内容第一部份:...

Qt快速入门(工程的创建、UI界面布局、多线程、项目)

本文档将介绍QT工程的创建、UI界面布局,并以计数器为例了解QT中多线程的用法,最终完成一个基础的QT项目。1创建QT工程文件在安装好QT之后,能够在其安装组件中找到QtCreator,点击设置项...

应用崩溃有救啦!Windows新更新将解决应用崩溃问题

【CNMO新闻】对于不少上班族来说,当自己的电脑在运行某个应用程序时,突然出现应用程序崩溃问题,常常会让人十分苦恼。尤其是对于设计师或者编辑,当自己的作品未能及时保存应用崩溃全部消失的时候,简直就是痛...

Python Qt GUI设计:窗口布局管理方法【强化】(基础篇—6)

在PythonQtGUI设计:窗口布局管理方法【基础篇】(基础篇—5)文章中,聊到了如何使用QtDesigner进行窗口布局管理,其实在QtDesigner中可以非常方便进行窗口布局管理设计,...

思考:如何设计游戏业务框架

虽然现在连主机游戏都纷纷加入了网战部分,不过其身份主要充当状态同步,矛盾点集中在同步即时性上。以大量数值逻辑为主的业务功能侧重点则不同。如果说写代码就是用状态的操作给问题建模,那么编程范式和设计模式种...

用.NET设计一个假装黑客的屏幕保护程序

本文主要介绍屏幕保护程序的一些相关知识,以及其在安全方面的用途,同时介绍了如何使用.NET开发一款屏幕保护程序,并对核心功能做了介绍,案例代码开源:https://github.com/sangy...

光的艺术:灯具创意设计

本文转自|艺术与设计微信号|artdesign_org_cn“光”是文明的起源,是思维的开端,同样也是人类睁眼的开始。每个人在出生一刻,便接受了光的照耀和洗礼。远古时候,人们将光奉为神明,用火来...

Python Qt GUI设计:将UI文件转换Python文件三种妙招(基础篇—2)

在开始本文之前提醒各位朋友,Python记得安装PyQt5库文件,Python语言功能很强,但是Python自带的GUI开发库Tkinter功能很弱,难以开发出专业的GUI。好在Python语言的开放...