通用线程:POSIX 线程详解(3)

2016-02-19 15:55 7 1 收藏

今天图老师小编要向大家分享个通用线程:POSIX 线程详解(3)教程,过程简单易学,相信聪明的你一定能轻松get!

【 tulaoshi.com - 编程语言 】


  第 3 部分
  
  内容:
  
  条件变量
  pthread_cond_wait() 小测验
  初始化和清除
  等待
  发送信号和广播
  工作组
  队列
  data_control 代码
  调试时间
  工作组代码
  代码初排
  有关清除的注重事项
  创建工作
  threadfunc()
  join_threads()
  结束语
  参考资料
  关于作者
  
  
  相关内容:
  
  第 2 部分:称作互斥对象的小玩意
  第 1 部分:POSIX 线程详解
  
  使用条件变量提高效率
  作者:Daniel Robbins
  
  
  本文是 POSIX 线程三部曲系列的最后一部分,Daniel 将具体讨论如何使用条件变量。条件变量是 POSIX 线程结构,可以让您在碰到某些条件时“唤醒”线程。可以将它们看作是一种线程安全的信号发送。Daniel 使用目前您所学到的知识实现了一个多线程工作组应用程序,本文将围绕着这一示例而进行讨论。
  
  条件变量详解
  在上一篇文章结束时,我描述了一个比较非凡的难题:假如线程正在等待某个特定条件发生,它应该如何处理这种情况?它可以重复对互斥对象锁定和解锁,每次都会检查共享数据结构,以查找某个值。但这是在浪费时间和资源,而且这种繁忙查询的效率非常低。解决这个问题的最佳方法是使用 pthread_cond_wait() 调用来等待非凡条件发生。
  
  了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。
  
  首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧是空的。这一特定线程什么也干不了 -- 其设计意图是从列表中除去节点,但是现在却没有节点。因此,它只能:
  
  锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。
  
  pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改已链接列表),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将清醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。
  
  此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它清醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。
  
  现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即清醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将清醒。
  
  现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并答应 1 号线程继续执行。那时,它可以马上检查列表,查看它所感爱好的更改。
  
  停止并回顾!
  那个过程非常复杂,因此让我们先往返顾一下。第一个线程首先调用:
  
  
  
  pthread_mutex_lock(&mymutex);
  
  
  
  
  然后,它检查了列表。没有找到感爱好的东西,于是它调用:
  
  
  
  pthread_cond_wait(&mycond, &mymutex);
  
  
  
  
  
  然后,pthread_cond_wait() 调用在返回前执行许多操作:
  
  
  
  pthread_mutex_unlock(&mymutex);
  
  
  
  
  它对 mymutex 解锁,然后进入睡眠状态,等待 mycond 以接收 POSIX 线程“信号”。一旦接收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自 pthread_cond_signal() 或 pthread_cond_broadcast() 调用的信号),它就会清醒。但 pthread_cond_wait() 没有立即返回 -- 它还要做一件事:重新锁定 mutex:
  
  
  
  pthread_mutex_lock(&mymutex);
  
  
  
  
  pthread_cond_wait() 知道我们在查找 mymutex “背后”的变化,因此它继续操作,为我们锁定互斥对象,然后才返回。
  
  pthread_cond_wait() 小测验
  现在已回顾了 pthread_cond_wait() 调用,您应该了解了它的工作方式。应该能够叙述 pthread_cond_wait() 依次执行的所有操作。尝试一下。假如理解了 pthread_cond_wait(),其余部分就相当轻易,因此请重新阅读以上部分,直到记住为止。好,读完之后,能否告诉我在调用 pthread_cond_wait() 之前,互斥对象必须处于什么状态?pthread_cond_wait() 调用返回之后,互斥对象处于什么状态?这两个问题的答案都是“锁定”。既然已经完全理解了 pthread_cond_wait() 调用,现在来继续研究更简单的东西 -- 初始化和真正的发送信号和广播进程。到那时,我们将会对包含了多线程工作队列的 C 代码了如指掌。
  
  初始化和清除
  条件变量是一个需要初始化的真实数据结构。以下就初始化的方法。首先,定义或分配一个条件变量,如下所示:
  
  
  
  
  pthread_cond_t mycond;
  
  
  
  
  然后,调用以下函数进行初始化:
  
  
  
  
  pthread_cond_init(&mycond,NULL);
  
  
  
  
  瞧,初始化完成了!在释放或废弃条件变量之前,需要毁坏它,如下所示:
  
  
  
  
  pthread_cond_destroy(&mycond);
  
  
  
  
  很简单吧。接着讨论 pthread_cond_wait() 调用。
  
  等待
  一旦初始化了互斥对象和条件变量,就可以等待某个条件,如下所示:
  
  
  
  
  pthread_cond_wait(&mycond, &mymutex);
  
  
  
  
  请注重,代码在逻辑上应该包含 mycond 和 mymutex。一个特定条件只能有一个互斥对象,而且条件变量应该表示互斥数据“内部”的一种非凡的条件更改。一个互斥对象可以用许多条件变量(例如,cond_empty、cond_full、cond_cleanup),但每个条件变量只能有一个互斥对象。
  
  发送信号和广播
  对于发送信号和广播,需要注重一点。假如线程更改某些共享数据,而且它想要唤醒所有正在等待的线程,则应使用 pthread_cond_broadcast 调用,如下所示:
  
  
  
  
  pthread_cond_broadcast(&mycond);
  
  
  
  
  在某些情况下,活动线程只需要唤醒第一个正在睡眠的线程。假设您只对队列添加了一个工作作业。那么只需要唤醒一个工作程序线程(再唤醒其它线程是不礼貌的!):
  
  
  
  
  pthread_cond_signal(&mycond);
  
  
  
  
  此函数只唤醒一个线程。假如 POSIX 线程标准答应指定一个整数,可以让您唤醒一定数量的正在睡眠的线程,那就更完美了。但是很可惜,我没有被邀请参加会议。
  
  工作组
  我将演示如何创建多线程工作组。在这个方案中,我们创建了许多工作程序线程。每个线程都会检查 wq(“工作队列”),查看是否有需要完成的工作。假如有需要完成的工作,那么线程将从队列中除去一个节点,执行这些特定工作,然后等待新的工作到达。
  
  与此同时,主线程负责创建这些工作程序线程、将工作添加到队列,然后在它退出时收集所有工作程序线程。您将会碰到许多 C 代码,好好预备吧!
  
  队列
  需要队列是出于两个原因。首先,需要队列来保存工作作业。还需要可用于跟踪已终止线程的数据结构。还记得前几篇文章(请参阅本文结尾处的参考资料)中,我曾提到过需要使用带有特定进程标识的 pthread_join 吗?使用“清除队列”(称作 "cq")可以解决无法等待任何已终止线程的问题(稍后将具体讨论这个问题)。以下是标准队列代码。将此代码保存到文件 queue.h 和 queue.c:
  
  queue.h
  /* queue.h
  ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
  
   ** Author: Daniel Robbins
  ** Date: 16 Jun 2000
  */
  
  typedef strUCt node {
  struct node *next;
  } node;
  

来源:http://www.tulaoshi.com/n/20160219/1610699.html

延伸阅读
虽然集成开发环境(IDE)可以为图形化应用程序提供很好的调试设置,但是它不允许你调试多线程的Java服务器程序。 幸运的是,有几个工具可以做到,例如日志应用程序接口(API)和Java调试器。开发人员也可以获得系统的线程转储,它可以在任何时间显示出系统状态。 为了得到系统线程转储,运行服务器程序并键入[Ctrl] []。这会输出所有...
实现Runnable接口的类必须使用Thread类的实例才能创建线程。通过Runnable接口创建线程分为两步:     1. 将实现Runnable接口的类实例化。     2. 建立一个Thread对象,并将第一步实例化后的对象作为参数传入Thread类的构造方法。     最后通过Thread类的start方法建立线程。   ...
本文包括以下内容: 单线程规则:Swing线程在同一时刻仅能被一个线程所访问。一般来说,这个线程是事件派发线程(event-dispatching thread)。 规则的例外:有些操作保证是线程安全的。 事件分发:假如你需要从事件处理(event-handling)或绘制代码以外的地方访问UI,那么你可以使用SwingUtilities类的invokeLate...
Thread Scheduling In Java technology,threads are usually preemptive,but not necessarily Time-sliced(the process of giving each thread an equal amount of CPU time).It is common mistake to believe that "preemptive" is a fancy word for "does time-slicing". For the runtime on a Solaris Op...
与人有生老病死一样,线程也同样要经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中和这四种状态相关的方法。 代码如下: // 开始线程      public void start( );      public void run( );      // ...

经验教程

596

收藏

67
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部