`
gstarwd
  • 浏览: 1536769 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

LInux高级编程 - 线程(Threads)

阅读更多

LInux高级编程 - 线程(Threads)

ALP Chapter 4 线程(Threads)

  • 线程可以简单理解成为进程的下级。一个系统可以有多个进程,一个进程内部可以有多个线程。
  • 回想上一章讲过的新进程的创建。先是fork,相当于拷贝了一个新的进程,然后调用exec,我们便有了两个毫不相关的进程。线程不一 样,当创建一个新的线程时,它和原来的线程是完全共享内存的。如果该线程修改了一个全局变量,则其他所有的线程读到的该变量的值都是修改后的。如果该线程 调用了exec,很不幸的,它的所有其他线程“兄弟”都会被终结。
4.1 线程的创建
  • 每个进程都有一个进程id,类似的,每个线程也有一个线程id,在C/C++里面,表示线程id的数据类型是pthread_t。
  • 线程函数(thread function)是一个类型为void* (*) (void*)的函数,线程被创建之后,就执行该函数内的代码。当该函数返回时,线程即结束。\
  • 创建线程的函数时pthread_create,这个函数有以下四个参数:
    • 一个指向pthread_t变量的指针,新创建的线程的id将保存在这里
    • 一个指向线程属性(thread attribute)的指针
    • 一个指向线程函数的指针void* (*) (void*)
    • 一个类型为void*的指针,指向传递给线程函数的参数
  • wait用来等待一个进程的结束,类似的作用于线程的函数名字叫做pthread_join。它有两个参数,等待的线程id(pthread_t)以及该线程的返回值(void*)。
  • pthread_self()返回当前的线程id,pthread_equal()可以用来比较两个线程id。这两个函数相当有用,它 们通常在调用pthread_join之前被调用,因为一个线程如果pthread_join自己的话很显然是会产生死锁的。(事实上也是如 此,pthread_join会返回EDEADLCK的错误)
  • 创建线程属性(Process attribute)的步骤:
    • 创建一个pthread_attr_t类型的变量
    • 调用pthread_attr_init,参数为上一步创建的变量的指针
    • 修改pthread_attr_t,使之包含我们需要的属性
    • 将pthread_attr_t传入pthread_create
    • 完成之后调用pthread_attr_destroy,参数为第一步创建的变量的指针。
  • 对于大部分的程序(所谓的“大部分的”之外的那些就是一些实时程序),我们用的上的线程属性就是它的分离状态(detach state)。
    • 一个线程可以是可加入的(joinable thread)或者是可分离的(detached thread)。可加入的线程在结束后,如果没有别的线程对其使用pthread_join,则它会挂起并等待,直到有线程调用 pthread_join,它的资源才会被释放。可分离的线程结束后,它的资源可被立即回收,别的线程无法通过pthread_join来保证和它的同 步,或者获得它的返回值。
    • 我们可以通过函数pthread_attr_setdetachstate来设置一个线程的分离状态。
    • 即使一个线程创建的时候是可加入的,我们也可以调用pthread_detach来把它设为可分离的。设为可分离的线程不能再重新设为可加入的。
4.2 线程的扼杀(Thread Cancellation)
  • 一个线程结束自己的方法有两种:从线程函数返回,或者调用pthread_exit,可惜这两种都不是我们这里要讨论的对象。除此之外,线程还可 以要求另一个线程中止,称为扼杀(canceling)一个线程(这就是为什么我不把cancellation翻译成取消的原因)。
  • 扼杀一个线程只要调用pthread_cancel即可。如果那个线程是可加入的,你可以先加入之,杀掉,然后释放它的资源(这里的资源指的是线程属性)
  • 随便杀人是不对的,随便杀线程也是不对的。如果一个线程自己申请了一些资源,然后莫名其妙被杀了,那些资源就不能释放了。我们不能阻止别人的杀人行为,但可以增强自己的防御能力,线程也可以增强自己的防御能力,它可以把自己声明为以下三种状态之一:
    • 异步可杀的(asynchronously cancelable):线程可以在任何时间被杀。
    • 同步可杀的(synchronously cancelable):线程可以被杀,但它不会马上咽气,它会坚持继续做自己的事情(例如电视中的说一大堆无用而煽情的对白),然后在到达某一个特定的 位置之后(比如在说到凶手名字之前那一霎那),再死掉。我们称这些特定的位置为扼杀点(cancellation points)。
    • 无敌的(uncancelable):免疫一切暗杀魔法,反正就是杀不掉。
    • 线程创建后的默认状态是同步可杀的。
  • 我们可以使用pthread_setcanceltype来将线程的扼杀状态设为上面三种的一种。对于扼杀点的创建,函数 pthread_testcancel可以创建,这个函数除了创建扼杀点之外不做任何事情。另外,一些其他函数的内部实现可能会创建扼杀点,所以调用这些 函数也会相当于创建了一个扼杀点。可以参考pthread_cancel的man page来查看这些函数的列表。
  • 线程保护自己的更为强大的一种手段是pthread_setcancelstate函数,该函数可以禁止/激活扼杀模式。在禁止扼 杀模式的情况下,我们可以放心的写一些critical section的代码。需要注意的是,在离开critical section后,应立即恢复到原来的扼杀模式。
4.3 线程副本数据(Thread-Specific Data)
  • 我们知道,对于全局变量,所有线程是共享的。对于局部变量,各个线程是独享的。介于这两者之间的是线程副本变量(thread-specific variable),每个线程都能够访问这些变量,不同的是每个线程都有各自唯一的一份拷贝。
  • 线程副本变量的读取/修改只能通过特定的函数接口来实现
    • 每个变量都必须有一个对应的key,key的类型是pthread_key_t,可以通过pthread_key_create来创建。 pthread_key_create的第一个参数就是一个pthread_key_t的指针,第二个参数是一个清理函数。该函数会在线程结束的时候被调 用(即使该线程是被扼杀的)。
    • 有了key之后就可以通过pthread_setspecific和pthread_getspecific来set/get线程副本变量了。
  • 线程副本数据的清理函数非常有用,用户甚至会专门把一些变量声明为副本数据来使得他们的清理函数可以自动被调用。事实上这样做是多余的,因为我们有一套独立的清理函数机制:
    • 清理函数和前面的一样,是void * (*) (void*)类型的
    • 用pthread_clearnup_push函数来注册清理函数,该函数的第二个参数将会做为清理函数的参数
    • 用pthread_clearnup_pop来注销已注册的清理函数,该函数有一个参数,如果该参数不为0,则在注销清理函数之前,会先调用该清理函数。
  • 在C++中,大家通常习惯于使用析构函数(destructor)来释放资源,因为普遍的观点是析构函数总是会被调用。其实这是错误的 观点,当一个线程调用了pthread_exit,C++并不保证所有在栈(Stack)上的变量的析构函数都会被调用!一个聪明的办法是,我们不直接调 用pthread_exit,而是抛出一个异常,在该异常的处理函数里面调用pthread_exit

4.4 同步(Synchronization)和关键部分(Critical Sections)

  • 这两个概念出现的地方实在是太多太多了,看得眼皮都起茧了。原理这里就不说了,我们还是集中看看linux是怎么实现这两个概念的吧。
  • Mutex的使用
    • 类型为pthread_mutex_t
    • 初始化的时候调用pthread_mutex_init函数,第一个参数为pthread_mutex_t *,第二个参数为mutex的attribute的指针,类似于thread的初始化,我们可以直接指定NULL。
    • 如果觉得pthread_mutex_init太繁琐,并且你只想使用默认属性的mutex,则把mutex赋值为PTHREAD_MUTEX_INITIALIZER就可以了。
    • 对应的加锁和解锁的函数为pthread_mutex_lock和pthread_mutex_unlock,参数都是pthread_mutex_t *
  • Mutex的类型
    • fast mutex(缺省):如果被同一个线程lock一次以上,就会有deadlock
    • recursive mutex:可以被同一个线程多次lock,但是记住必须调用同样次数的pthread_mutex_unlock
    • error-checking mutex:如果同一个线程由于进行了一次以上的lock而造成死锁的话,那么第二次以及第二此之后的lock会返回EDEADLK的错误值
    • 指定Mutex类型的代码:
      pthread_mutexattr_t attr;
      pthread_mutex_t mutex;
      pthread_mutexattr_init (&attr);
      // recursive mutex则使用PTHREAD_MUTEX_RECURSIVE_NP
      pthread_mutexattr_setkind_np (&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
      pthread_mutex_init (&mutex, &attr);
      pthread_mutexattr_destroy (&attr);
  • block版本的lock函数pthread_mutex_lock无疑是低效的,非block版本的lock函数是pthread_mutex_trylock
  • Semaphore的使用
    • Semaphore的类型是sem_t
    • 初始化的函数是sem_init,第一个参数是sem_t的指针,第二个参数是0(非0代表该semaphore可以被多个进程共享,而目前的GNU不支持这个feature),第三个参数是sem_t的初始值
    • 操作函数分别是sem_wait和sem_post(有些书上也把post叫做signal)
  • 条件变量(Condition Variables)
    • 条件变量也是一种实现同步的机制,两个相关的操作是wait和signal。
    • 条件变量没有计数器,也不占内存。因此wait必须在signal之前,如果signal的时候没有正在wait的thread,则signal丢失。
    • 每个条件变量通常都和一个mutex联合使用
  • 条件变量的使用
    • 条件变量的类型是pthread_cond_t
    • 初始化的函数是pthread_cond_init,第一个参数是pthread_cond_t的指针,第二个参数是条件变量的属性的指针。
    • 操作函数分别是pthread_cond_wait和pthread_cond_signal。这里需要注意的是 pthread_cond_wait需要的第二个参数是一个mutex,并且在调用wait之前该mutex必须是被lock的,wait函数会自动 unlock该mutex,并且在wait成功后自动继续lock该mutex。

4.5 GNU/Linux中线程的实现

  • 这里之所以要特别提出实现问题是因为GNU/Linux对POSIX线程的实现不同于其他Unix平台。
    • 当我们调用pthread_create来创建线程的时候,系统创建的是一个新的进程,并且在该进程内创建我们需要的线程。
    • 这个新创建的进程和用fork创建出来的进程不同,他和原来的进程是共享内存的,而不是像fork出来的那样是做了一份copy的。
    • 其实在第一次调用pthread_create的时候,系统还会创建一个进程,叫做管理进程(manager thread) ,这牵涉到GNU/Linux的内部实现问题,我们就不展开了。
    • 因此,如果我们有一个程序./pthread-pid,该程序只是调用pthread_create来创建一个新的线程,并且使用无限 循环来保证主线程和新的线程都不结束。执行该程序后,我们使用ps来查看,就会发现三个./pthread-pid进程,一个是我们执行的,一个是 pthread_create创建出来的,另一个就是管理进程。
  • Signal的处理
    • Signal发送的单位是进程,恰好GNU/Linux里面线程也是用进程实现的,因此在向一个多线程的程序发送Signal的时候,不会出现无法识别该由哪个线程来负责接受Signal的问题。
    • 在一个多线程程序中,线程之间也可以发送Signal,方法是pthread_kill函数。第一个参数是线程ID,第二个参数是signal number
  • 我们前面提到pthread_create会创建一个和当前进程共享内存的新进程,而fork会创建一个获得当前进程copy的进程。那么pthread_create是怎么做到的?
    • 其实有一个函数是pthread_create和fork的统一形式,该函数叫clone,clone可以指定在新的进程和旧的进程之间有哪些东西是共享的,哪些东西是copy的。
    • 这里提出clone只是为了满足大家的好奇心罢了,一般来说,在我们的程序中应该尽量避免使用这个函数。

4.6 进程Vs. 线程

  • 这是一个永恒的话题,新手们总是弄不清楚在一个需要多任务并发的程序中应该使用进程还是线程。这里列出了他们之间的区别:
    • 一个程序中的所有线程必须执行同一个可执行文件。一个新的子进程可以去执行和父进程不同的可执行文件(fork接exec是通常的做法)。
    • 在线程中,一个老鼠屎坏了一锅汤,如果一个线程出现了问题,其他所有的线程都会收到影响,因为他们共享内存。而进程彼此间独立,不存在这样的问题。
    • 创建一个进程比创建一个线程的代价要大,因为需要额外的内存拷贝的操作。虽然在现在绝大多数操作系统中,都实现了copy-on-write的技术,但进程的创建还是要比线程更费时一些。
    • 如果一个多任务的程序的各个任务是紧耦合的,或者说几乎相同的,线程将是更好的选择。如果各个任务直接没什么紧密的联系,那使用进程会更恰当一些。
    • 在线程之间共享数据是非常容易的,因为他们本身就是内存共享的。而在进程之共享数据要麻烦的多,还要使用一种叫做IPC的机制(我们下章会详细描述),但是进程之间的数据共享对于死锁等同步问题具有更强的抵抗能力。
 原文地址 http://colar.spaces.live.com/blog/cns!55D4B10191447DCB!345.entry

 

 原文地址 http://blog.chinaunix.net/u2/75758/showart_1772356.html

分享到:
评论

相关推荐

    linux多线程编程.pdf

    Linux多线程编程是现代操作系统编程的重要组成部分,尤其是在Linux环境下,多线程编程更是成为了高性能应用不可或缺的技术之一。本文档主要涉及Linux多线程编程的一些关键知识点,包括pthread线程库的使用、线程的...

    Linux多线程 C语言编程关于多线程

    ### Linux多线程 C语言编程知识点总结 #### 一、多线程与多进程编程概述 **多线程**的概念可以追溯到20世纪60年代,然而直到80年代中期,这一机制才被正式引入到Unix系统中。随着计算机硬件的发展及软件需求的增加...

    linux下C语言多线程编程实例

    在Linux环境下进行C语言多线程编程,可以利用POSIX线程库,也称为pthread库。这个库提供了创建、同步和管理线程的一系列接口,使得开发者能够在单个进程中同时执行多个任务,从而提高程序的并行性。下面将详细探讨多...

    多线程编程 UNIX LINUX Programming with POSIX Threads

    在大中华区,我相信此书网上仅此一本.由本人制作 . 我在很多论坛上看到类似的主题:求书:...希望对大家有帮助. 文件太大,分成了四部分,请下载完整. <br>多线程编程 UNIX LINUX Programming with POSIX Threads

    Python库 | signalr-client-threads-0.0.12.tar.gz

    总之,`signalr-client-threads-0.0.12`是一个方便的Python库,为Python开发者提供了在多线程环境中与SignalR服务交互的能力,从而能够在Python应用中实现实时通信功能。在实际项目中,正确理解和使用这个库将有助于...

    (中英文)-POSIX多线程程序设计-Programming with POSIX Threads

    《Programming with POSIX Threads》是David Butenhof撰写的一本经典教程,主要针对Unix/Linux环境下的多线程程序设计。本书深入浅出地介绍了POSIX线程(也称为pthreads)API,是理解并掌握多线程编程的重要参考资料...

    多线程编程 UNIX LINUX Programming with POSIX Threads 第3部分

    在大中华区,我相信此书网上仅此一本.由本人制作 . 我在很多论坛上看到类似的主题:求书:...希望对大家有帮助. 文件太大,分成了四部分,请下载完整. <br>多线程编程 UNIX LINUX Programming with POSIX Threads

    UNIX环境高级编程_Linux/Unix编程_

    了解如何有效地管理内存是UNIX/Linux编程的关键。书中会讲解动态内存分配(malloc、calloc、realloc、free等),以及内存映射(mmap)和内存对齐等概念。 八、系统接口和系统调用 书中将详细阐述系统调用接口,包括...

    Linux 线程实现分析

    - **《Linux多线程编程技术使用详解》**:这本书专门讨论了Linux下的多线程编程技术,包括各种线程模型的实现和使用技巧。 - **IBM DeveloperWorks 文章**:“Linux线程实现机制分析”,网址:...

    多线程编程 UNIX LINUX Programming with POSIX Threads 第2部分

    在大中华区,我相信此书网上仅此一本.由本人制作 . 我在很多论坛上看到类似的主题:求书:...希望对大家有帮助. 文件太大,分成了四部分,请下载完整. <br>多线程编程 UNIX LINUX Programming with POSIX Threads

    多线程编程 UNIX LINUX Programming with POSIX Threads 第4部分

    在大中华区,我相信此书网上仅此一本.由本人制作 . 我在很多论坛上看到类似的主题:求书:...希望对大家有帮助. 文件太大,分成了四部分,请下载完整. <br>多线程编程 UNIX LINUX Programming with POSIX Threads

    C语言Threads等程序(Use P-threads)

    本主题聚焦于使用C语言在Linux环境下实现线程编程,主要涉及了P-threads库,这是一种在Unix和类Unix系统中实现线程API的标准。下面将详细阐述相关知识点。 1. **C语言与线程编程**: C语言虽然没有内置的多线程支持...

    linux系统线程的结构分析

    线程模型通常分为核心级线程(Kernel-Level Threads, KLTs)和用户级线程(User-Level Threads, ULTs)。核心级线程由内核调度,更适合SMP系统,而用户级线程的调度在用户态完成,上下文切换开销小。现代操作系统...

    Linux多线程编程知识点总结(C语言)(csdn)————程序.pdf

    Pthreads(POSIX Threads)是Linux下多线程编程的标准接口,主要包含四部分: 1) 线程管理:包括线程创建、终止、等待、分离和设置属性。`pthread_create()`用于创建线程,`pthread_exit()`终止线程。线程可以是分离...

    3_threads_write_file.rar_linux综合_threads_线程综合例子

    在Linux系统中,线程编程是一项重要的技能,它允许程序并行执行多个任务,从而提高效率。本示例"3_threads_write_file.rar"是关于在Linux下如何使用线程进行文件写入操作的一个综合实例。这个例子将帮助你深入理解和...

    Unix环境高级编程第三版 English version

    《Unix环境高级编程》第三版是关于Unix系统编程的一部经典作品,它被誉为Linux系统编程的神书。在讨论这本书时,我们需要了解它所涵盖的广泛知识点和它在Unix系统编程领域中的重要性。 首先,Unix系统编程涉及在...

    C++(Qt)软件调试-线程死锁调试(15)

    在C++编程中,特别是在使用Qt进行多线程开发时,死锁是一个常见的问题,它会导致程序无法继续执行。死锁通常发生在两个或多个线程互相等待对方释放资源的情况下。解决这个问题需要对线程同步机制有深入理解,并掌握...

    Linux 多线程开发技术

    在Linux环境下进行多线程编程时,开发者面临的一个关键选择是采用用户级线程(User-Level Threads, ULTs)还是内核级线程(Kernel-Level Threads, KLTs)。这两种线程管理模式各有优势和局限性,理解它们之间的区别...

Global site tag (gtag.js) - Google Analytics