`

Linux 的多线程编程

阅读更多
本文们针 Linux 线程编程主特性总结出 5 条经验,以改善 Linux 线程编程习惯避免其陷阱。本文,们穿插些 Windows 编程例以比 Linux 特性,以加深读印象。

   背景

   Linux 线程程序相应其(比如 Windows)线程 API 有些细微隐晦差别。不注意这些 Linux 些陷阱,常常致程序问题不穷,死锁不断。本文们从 5 个方面总结出 Linux 线程编程问题,并分别引出相关改善经验,以避免这些陷阱。们希望这些经验以帮助读们能更好更快熟悉 Linux 线程编程。

   们假设读都已经熟悉 Linux 基本线程编程 Pthread 库 API 。其第三方以线程编程库,如 boost,不本文提及。本文主涉及题材包括线程线程管理,互斥变量,条变量等。进程概念不本文涉及。

   Linux 线程 API 概介绍

   线程 Linux 已经有成熟 Pthread 库支持。其涉及线程最基本概念主包含三点:线程,互斥锁,条。其,线程操作又分线程创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别创建,销毁,加锁解锁。条操作有 5 种操作:创建,销毁,触发,广播等待。其些线程扩展概念,如信号灯等,都以通过面三个基本元素基本操作封装出。

   线程,互斥锁,条 Linux 应 API 以表 1 归纳。方便熟悉 Windows 线程编程读熟悉 Linux 线程 API,们表同时列出 Windows SDK 库所应 API 名称。

   表 1. 线程函数列表

象 操作 Linux Pthread API Windows SDK 库应 API
线程 创建 pthread_create CreateThread
退出 pthread_exit ThreadExit
等待 pthread_join WaitForSingleObject
互斥锁 创建 pthread_mutex_init CreateMutex
销毁 pthread_mutex_destroy CloseHandle
加锁 pthread_mutex_lock WaitForSingleObject
解锁 pthread_mutex_unlock ReleaseMutex
条 创建 pthread_cond_init CreateEvent
销毁 pthread_cond_destroy CloseHandle
触发 pthread_cond_signal SetEvent
广播 pthread_cond_broadcast SetEvent / ResetEvent
等待 pthread_cond_wait / pthread_cond_timedwait SingleObjectAndWait


   线程 Linux 已经有成熟 Pthread 库支持。其涉及线程最基本概念主包含三点:线程,互斥锁,条。其,线程操作又分线程创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别创建,销毁,加锁解锁。条操作有 5 种操作:创建,销毁,触发,广播等待。其些线程扩展概念,如信号灯等,都以通过面三个基本元素基本操作封装出。

   Linux 线程编程 5 条经验

   尽量设置 recursive 属性以始化 Linux 互斥变量

   互斥锁线程编程基本概念,被广泛使。其调次序层次清晰简单:建锁,加锁,解锁,销毁锁。但需注意,诸如 Windows 互斥变量不同,默认,Linux 同线程无法同互斥锁进行递归加速,否则发生死锁。

   所谓递归加锁,就同线程试图互斥锁进行两次或两次以行。其场景 Linux 代码由清单 1 所示。

   清单 1. Linux 重复互斥锁加锁实例

   // 通过默认条建锁
  pthread_mutex_t *theMutex = new pthread_mutex_t; 
  pthread_mutexattr_t attr; 
  pthread_mutexattr_init(&attr); 
  pthread_mutex_init(theMutex,&attr); 
  pthread_mutexattr_destroy(&attr); 

  // 递归加锁
  pthread_mutex_lock (theMutex); 
  pthread_mutex_lock (theMutex); 
  pthread_mutex_unlock (theMutex); 
  pthread_mutex_unlock (theMutex);

   以代码场景,问题出现第二次加锁操作。由于默认,Linux 不同线程递归加锁,因此第二次加锁操作时线程出现死锁。

   Linux 互斥变量这种奇怪行或许于特定某些场景所有处,但于数看起更像程序个 bug 。毕竟,同线程同互斥锁进行递归加锁尤其二次经常需。

   这个问题互斥锁默认 recursive 属性有关。解决问题就显式地互斥变量始化时设置起 recursive 属性。基于此,以代码其实稍作修改就以好运行,只需始化锁时候加设置个属性。看清单 2 。

   清单 2. 设置互斥锁 recursive 属性实例

   pthread_mutexattr_init(&attr); 
  // 设置 recursive 属性
  pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE_NP); 
  pthread_mutex_init(theMutex,&attr);

   因此,建议尽量设置 recursive 属性以始化 Linux 互斥锁,这样既以解决同线程递归加锁问题,又以避免死锁发生。这样做还有个额好处,就以让 Windows  Linux 让锁表现统。

  注意 Linux 触发条变量自动复位问题

   条变量置位复位有两种常模型:第种模型当条变量置位(signaled)以,如果当没有线程等待,其状态保持置位(signaled),直有等待线程进入被触发,其状态才变复位(unsignaled),这种模型采以 Windows  Auto-set Event 代表。其状态如图 1 所示:

   图 1. Windows 条变量状态流程





   通过比结果,同样逻辑, Linux 运行结果却完全两样。于 Windows 模型, Jack 开着出租车站台,触发条变量。如果没顾客,条变量维持触发状态,就说 Jack 停车那里等着。直 Susan 姐站台,执行等待条找出租车。 Susan 搭 Jack 出租车离开,同时条变量被自动复位。

   但 Linux ,问题就,Jack 站台看没人,触发条变量被直接复位,于 Jack 排等待队列里面。迟秒 Susan 姐站台却看不那里等待 Jack,只能等待,直 Mike 开车赶,重新触发条变量,Susan 才 Mike 车。这于排队系统面 Jack 不公平,而问题症结于 Linux 条变量触发自动复位引起个 Bug 。

   条变量 Linux 这种模型难说好坏。但实际,们以代码稍加改进就以避免这种差异发生。由于这种差异只发生触发没有被线程等待条变量时刻,因此们只需掌握好触发时机即。最简单做法增加个计数器记录等待线程个数,决定触发条变量检查该变量即。改进 Linux 函数如清单 5 所示。

   清单 5. Linux 出租车案例代码实例

   ……
 // 提示出租车达条变量
 pthread_cond_t taxiCond; 

 // 同步锁
 pthread_mutex_t taxiMutex; 

 // 旅客人数,始 0 
 int travelerCount=0; 

 // 旅客达等待出租车
 void * traveler_arrive(void * name) { 
  cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl; 
  pthread_mutex_lock(&taxiMutex); 

  // 提示旅客人数增加
  travelerCount++; 
  pthread_cond_wait (&taxiCond, &taxiMutex); 
  pthread_mutex_unlock (&taxiMutex); 
  cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl; 
  pthread_exit( (void *)0 ); 
 } 

 // 出租车达
 void * taxi_arrive(void *name) 
 { 
  cout<< ” Taxi ” <<(char *)name<< ” arrives. ” <<endl; 

 while(true) 
 { 
    pthread_mutex_lock(&taxiMutex); 

    // 当已经有旅客等待时,才触发条变量
    if(travelerCount>0) 
    { 
      pthread_cond_signal(&taxtCond); 
      pthread_mutex_unlock (&taxiMutex); 
      break; 
    } 
    pthread_mutex_unlock (&taxiMutex); 
  } 

  pthread_exit( (void *)0 ); 
 }

   因此们建议 Linux 出发条变量检查否有等待线程,只有当有线程等待时才条变量进行触发。

  注意条返回时互斥锁解锁问题

    Linux 调 pthread_cond_wait 进行条变量等待操作时,们增加个互斥变量参数必,这避免线程间竞争饥饿。但当条等待返回时候,需注意定不遗漏互斥变量进行解锁。

   Linux  pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 函数返回时,互斥锁 mutex 处于锁定状态。因此如果需临界区数据进行重新访问,则没有必 mutex 就行重新加锁。但,随而问题,每次条等待以需加入步手动解锁操作。如文乘客等待出租车 Linux 代码如清单 6 所示:

   清单 6. 条变量返回解锁实例

   void * traveler_arrive(void * name) { 
  cout<< ” Traveler: ” <<(char *)name<< ” needs a taxi now! ” <<endl; 
  pthread_mutex_lock(&taxiMutex); 
  pthread_cond_wait (&taxiCond, &taxtMutex); 
  pthread_mutex_unlock (&taxtMutex); 
  cout<< ” Traveler: ” << (char *)name << ” now got a taxi! ” <<endl; 
  pthread_exit( (void *)0 ); 
 }

   这点于熟悉 Windows 线程说尤重。 Windows  SignalObjectAndWait() 函数常 Linux  pthread_cond_wait() 函数被看作跨编程时等价函数。但需注意,两个函数退出时状态不样。 Windows ,SignalObjectAndWait(HANDLE a, HANDLE b, …… ) 调结束返回时状态 a  b 都置位(signaled)状态,普遍使,a 经常个 Mutex 变量,这种,当返回时,Mutex a 处于解锁状态(signaled),Event b 处于置位状态(signaled), 因此,于 Mutex a 而言,们不需考虑解锁问题。而且, SignalObjectAndWait() ,如果需临界区数据进行重新访问,都需调 WaitForSingleObject() 重新加锁。这点刚好 Linux  pthread_cond_wait() 完全相反。

   Linux 于 Windows 这点额解锁操作区别重,定得牢记。否则从 Windows 移植 Linux 条等待操作旦忘结束解锁操作,程序肯定发生死锁。

   等待绝时间问题

   超时线程编程个常见概念。例如,当 Linux 使 pthread_cond_timedwait() 时就需指定超时这个参数,以便这个 API 调最只被阻塞指定时间间隔。但如果第次使这个 API 时,首先需解就这个 API 当超时参数特殊性(就如本节标题所提示那样)。们首先看这个 API 定义。 pthread_cond_timedwait() 定义看清单 7 。

   清单 7. pthread_cond_timedwait() 函数定义

   int pthread_cond_timedwait(pthread_cond_t *restrict cond, 
       pthread_mutex_t *restrict mutex, 
       const struct timespec *restrict abstime);

   参数 abstime 这里表示超时时间相关个参数,但需注意所表示个绝时间,而不个时间间隔数值,只有当系统当时间达或超过 abstime 所表示时间时,才触发超时事。这于拥有 Windows 线程经验人说能尤困惑。因 Windows 所有 API 等待参数(如 SignalObjectAndWait,等)都相时间,

   假设们指定相超时时间参数如 dwMilliseconds (单位毫秒)调超时相关函数,这样就需 dwMilliseconds 转化 Linux 绝时间参数 abstime 使。常转换如清单 8 所示:

   清单 8. 相时间绝时间转换实例

   /* get the current time */ 
  struct timeval now; 
  gettimeofday(&now, NULL); 


  /* add the offset to get timeout value */ 
  abstime ->tv_nsec = now.tv_usec * 1000 + (dwMilliseconds % 1000) * 1000000; 
  abstime ->tv_sec = now.tv_sec + dwMilliseconds / 1000;

   Linux 绝时间看似简单明,却个非常隐晦陷阱。而且旦忘时间转换,以想象,等待误么令人头疼:如果忘相时间转换成绝时间,相当于告诉系统所等待超时时间过去式 1970 年 1 月 1 号某个时间段,于操作系统毫不犹豫马送给个 timeout 返回值,然举着拳头抱怨什么另个同步线程耗时居然如此久,并头扎进寻找耗时原因深渊里。

  确处理 Linux 线程结束问题

    Linux ,当处理线程结束时需注意个问题就让个线程善始善终,让其所占资源得确释放。 Linux 默认,虽然各个线程间相互独立,个线程终止不去通知或影响其线程。但已经终止线程资源并不随着线程终止而得释放,们需调 pthread_join() 获得另个线程终止状态并且释放该线程所占资源。 Pthread_join() 函数定义如清单 9 。

   清单 9. pthread_join 函数定义

   int pthread_join(pthread_t th, void **thread_return);

   调该函数线程挂起,等待 th 所表示线程结束。 thread_return 指向线程 th 返回值指针。需注意 th 所表示线程必须 joinable ,即处于非 detached(游离)状态;并且只以有唯个线程 th 调 pthread_join() 。如果 th 处于 detached 状态,那么 th  pthread_join() 调返回误。

   如果压根儿不关心个线程结束状态,那么以个线程设置 detached 状态,从而让操作系统该线程结束时回收所占资源。个线程设置 detached 状态以通过两种方式实现。种调 pthread_detach() 函数,以线程 th 设置 detached 状态。其申明如清单 10 。

   清单 10. pthread_detach 函数定义

   int pthread_detach(pthread_t th);

   另种创建线程时就设置 detached 状态,首先始化个线程属性变量,然其设置 detached 状态,最作参数传入线程创建函数 pthread_create(),这样所创建出线程就直接处于 detached 状态。如清单 11 。

   清单 11. 创建 detach 线程代码实例

   ………………………………… .. 
  pthread_t    tid; 
  pthread_attr_t attr; 
  pthread_attr_init(&attr); 
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 
  pthread_create(&tid, &attr, THREAD_FUNCTION, arg);

   总使 Pthread 时避免线程资源线程结束时不能得确释放,从而避免产生潜存泄漏问题,待线程结束时,确保该线程处于 detached 状态,否着就需调 pthread_join() 函数其进行资源回收。

   总结补充

   本文以部分详细介绍 Linux 线程编程 5 条效经验。另以考虑尝试其些开源类库进行线程。

   1. Boost 库

   Boost 库自于由 C++ 标准委员类库工作组成员发起,致力于 C++ 新类库 Boost 组织。虽然该库本身并不针线程而产生,但发展至今,其已提供比较全面线程编程 API 支持。 Boost 库于线程支持 API 风格更类似于 Linux  Pthread 库,差别于其线程,互斥锁,条等线程概念都封装成 C++ 类,以方便调。 Boost 库目跨支持不,不仅支持 Windows  Linux ,还支持各种商 Unix 版本。如果想使稳定性统线程编程接口减轻跨难度, Boost 库首选。

   2. ACE

   ACE 全称 ADAPTIVE Communication Environment,个免费,开源,面向象工具框架,以并发访问软。由于 ACE 最面向网络服务端编程,因此于线程工具库能提供全面支持。其支持全面,包括 Windows,Linux 各种版本 Unix 。 ACE 唯问题如果仅仅于线程编程,其似显得有些过于重量级。而且其较复杂配置让其部署学而言并非易事。

  • 大小: 4.6 KB
分享到:
评论

相关推荐

    嵌入式软件开发技术:第5章 嵌入式Linux多线程编程.ppt

    嵌入式Linux多线程编程 嵌入式Linux多线程编程是嵌入式系统开发中的一种重要技术,能够提高系统的效率和响应速度。本章节将详细介绍嵌入式Linux多线程编程的基本概念、线程的创建、同步和互斥、线程属性、多线程...

    linux多线程编程.pdf

    Linux多线程编程是操作系统中并发程序设计的一个重要领域,它允许开发者在同一程序中创建多个线程,以实现并行执行,从而提高程序的执行效率和响应能力。Linux下的多线程编程通常基于POSIX线程(pthread)库来实现,...

    Linux多线程编程手册

    Linux多线程编程是计算机编程中一个高级主题,涉及到同时执行多个任务的能力,这些任务共享公共地址空间。它允许程序更有效地利用多核处理器的能力,提高性能和响应速度,优化资源利用,并改善程序的结构。Linux多...

    linux 多线程编程

    Linux 多线程编程 Linux 多线程编程是操作系统课程设计的重要组成部分,多线程技术早在 60 年代就被提出,但真正应用多线程到操作系统中去,是在 80 年代中期,solaris 是这方面的佼佼者。传统的 Unix 也支持线程的...

    linux多线程编程概述.doc

    本篇将深入介绍Linux多线程编程的基本概念、实现方法以及注意事项。 首先,多线程是通过创建多个执行线程来实现并发执行的。每个线程都有自己的调用栈,可以独立执行代码,共享同一地址空间内的资源,如全局变量和...

    Linux多线程编程_linux_

    总之,Linux多线程编程涉及的内容广泛,包括线程的创建、同步、通信、资源管理等多个方面。掌握这些知识点对于开发高效、稳定的多线程应用程序至关重要。通过深入学习和实践,你将能够充分利用Linux的多线程特性来...

    linux多线程编程指南

    ### Linux多线程编程知识点详解 #### 一、多线程基础介绍 ##### 定义多线程术语 - **线程**:是进程中的一个执行单元,是进程内部的一个可调度实体。 - **进程**:是正在运行的程序实例,拥有独立的地址空间。 - ...

    实验二、嵌入式Linux多线程编程实验

    实验二的目的是让学生深入理解嵌入式Linux环境下的多线程编程,这涉及到对线程概念、创建和管理的理解,以及如何在编程中引入线程库。线程是操作系统资源调度的基本单位,允许在一个进程中并发执行多个执行路径,...

    linux多线程编程

    ### Linux多线程编程详解 在现代计算领域,多线程编程是提升程序效率与响应性的关键技巧之一,尤其在Linux环境下,其丰富的系统资源和强大的内核支持为多线程编程提供了肥沃的土壤。本文将深入探讨Linux多线程编程...

    linux多线程编程.doc

    在Linux系统中进行C语言的多线程编程,可以利用POSIX线程库(pthread库)提供的接口。...理解这些基本概念和函数是进行Linux多线程编程的基础,而通过实践编写和调试多线程程序,可以更好地理解和掌握这些知识。

    Linux多线程编程

    Linux多线程编程是操作系统中实现并发执行任务的一种机制,它允许在单个进程中创建多个执行线程。相比传统的进程模型,线程提供了更高效、轻量级的并发执行方式,因为它们共享同一地址空间,减少了上下文切换的开销...

    linux多线程编程ppt

    【Linux多线程编程】是Linux系统开发中的一个重要主题,主要涉及如何在Linux环境中创建、管理和同步线程。华清远见的这份PPT为学习者提供了深入的理解和实践指导。 在Linux系统中,线程是进程内的执行单元,是...

Global site tag (gtag.js) - Google Analytics