POSIX 线程详解(1)

2016-02-19 12:36 2 1 收藏

清醒时做事,糊涂时读书,大怒时睡觉,无聊时关注图老师为大家准备的精彩内容。下面为大家推荐POSIX 线程详解(1),无聊中的都看过来。

【 tulaoshi.com - 编程语言 】


  一种支持内存共享的简捷工具
  
  作者:Daniel Robbins
  
  内容:
  
  
  线程是有趣的
  线程是快捷的
  线程是可移植的
  第一个线程
  无父,无子
  同步漫游
  参考资料
  关于作者
  
  
  
  POSIX(可移植操作系统接口)线程是提高代码响应和性能的有力手段。在本系列中,Daniel Robbins 向您精确地展示在编程中如何使用线程。其中还涉及大量幕后细节,读完本系列文章,您完全可以运用 POSIX 线程创建多线程程序。
  
  线程是有趣的
  了解如何正确运用线程是每一个优秀程序员必备的素质。线程类似于进程。如同进程,线程由内核按时间分片进行治理。在单处理器系统中,内核使用时间分片来模拟线程的并发执行,这种方式和进程的相同。而在多处理器系统中,如同多个进程,线程实际上一样可以并发执行。
  
  那么为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序中的所有线程都可以读或写声明过的全局变量。假如曾用 fork() 编写过重要代码,就会熟悉到这个工具的重要性。为什么呢?虽然 fork() 答应创建多个进程,但它还会带来以下通信问题: 如何让多个进程相互通信,这里每个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不同种类的本地 IPC (进程间通信),但它们都碰到两个重要障碍:
  
  
  强加了某种形式的额外内核开销,从而降低性能。
  对于大多数情形,IPC 不是对于代码的“自然”扩展。通常极大地增加了程序的复杂性。
  
  双重坏事: 开销和复杂性都非好事。假如曾经为了支持 IPC 而对程序大动干戈过,那么您就会真正欣赏线程提供的简单共享内存机制。由于所有的线程都驻留在同一内存空间,POSIX 线程无需进行开销大而复杂的长距离调用。只要利用简单的同步机制,程序中所有的线程都可以读取和修改已有的数据结构。而无需将数据经由文件描述符转储或挤入紧窄的共享内存空间。仅此一个原因,就足以让您考虑应该采用单进程/多线程模式而非多进程/单线程模式。
  
  线程是快捷的
  不仅如此。线程同样还是非常快捷的。与标准 fork() 相比,线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的 CPU 时间,使得线程创建比新进程创建快上十到一百倍。因为这一点,可以大量使用线程而无需太过于担心带来的 CPU 或内存不足。使用 fork() 时导致的大量 CPU 占用也不复存在。这表示只要在程序中有意义,通常就可以创建线程。
  
  当然,和进程一样,线程将利用多 CPU。假如软件是针对多处理器系统设计的,这就真的是一大特性(假如软件是开放源码,则最终可能在不少平台上运行)。特定类型线程程序(尤其是 CPU 密集型程序)的性能将随系统中处理器的数目几乎线性地提高。假如正在编写 CPU 非常密集型的程序,则绝对想设法在代码中使用多线程。一旦把握了线程编码,无需使用繁琐的 IPC 和其它复杂的通信机制,就能够以全新和创造性的方法解决编码难题。所有这些特性配合在一起使得多线程编程更有趣、快速和灵活。
  
  线程是可移植的
  假如熟悉 Linux 编程,就有可能知道 __clone() 系统调用。__clone() 类似于 fork(),同时也有许多线程的特性。例如,使用 __clone(),新的子进程可以有选择地共享父进程的执行环境(内存空间,文件描述符等)。这是好的一面。但 __clone() 也有不足之处。正如__clone() 在线帮助指出:
  
  “__clone 调用是特定于 Linux 平台的,不适用于实现可移植的程序。欲编写线程化应用程序(多线程控制同一内存空间),最好使用实现 POSIX 1003.1c 线程 API 的库,例如 Linux-Threads 库。参阅 pthread_create(3thr)。”
  
  虽然 __clone() 有线程的许多特性,但它是不可移植的。当然这并不意味着代码中不能使用它。但在软件中考虑使用 __clone() 时应当权衡这一事实。值得庆幸的是,正如 __clone() 在线帮助指出,有一种更好的替代方案:POSIX 线程。假如想编写可移植的多线程代码,代码可运行于 Solaris、FreeBSD、Linux 和其它平台,POSIX 线程是一种当然之选。
  
  第一个线程
  下面是一个 POSIX 线程的简单示例程序:
  
  
  thread1.c
  #include
  #include
  #include
  
  void *thread_function(void *arg) {
  int i;
  for ( i=0; i20; i++) {
  printf("Thread says hi!n");
  sleep(1);
  }
  return NULL;
  }
  
  int main(void) {
  
  pthread_t mythread;
  
  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
  printf("error creating thread.");
  abort();
  }
  
  if ( pthread_join ( mythread, NULL ) ) {
  printf("error joining thread.");
  abort();
  }
  
  exit(0);
  
  }
  
  
  
  
  要编译这个程序,只需先将程序存为 thread1.c,然后输入:
  
  $ gcc thread1.c -o thread1 -lpthread
  
  
  
  
  运行则输入:
  
  $ ./thread1
  
  
  
  
  理解 thread1.c
  thread1.c 是一个非常简单的线程程序。虽然它没有实现什么有用的功能,但可以帮助理解线程的运行机制。下面,我们一步一步地了解这个程序是干什么的。main() 中声明了变量 mythread,类型是 pthread_t。pthread_t 类型在 pthread.h 中定义,通常称为“线程 id”(缩写为 "tid")。可以认为它是一种线程句柄。
  
  mythread 声明后(记住 mythread 只是一个 "tid",或是将要创建的线程的句柄),调用 pthread_create 函数创建一个真实活动的线程。不要因为 pthread_create() 在 "if" 语句内而受其迷惑。由于 pthread_create() 执行成功时返回零而失败时则返回非零值,将 pthread_create() 函数调用放在 if() 语句中只是为了方便地检测失败的调用。让我们查看一下 pthread_create 参数。第一个参数 &mythread 是指向 mythread 的指针。第二个参数当前为 NULL,可用来定义线程的某些属性。由于缺省的线程属性是适用的,只需将该参数设为 NULL。
  
  第三个参数是新线程启动时调用的函数名。本例中,函数名为 thread_function()。当 thread_function() 返回时,新线程将终止。本例中,线程函数没有实现大的功能。它仅将 "Thread says hi!" 输出 20 次然后退出。注重 thread_function() 接受 void * 作为参数,同时返回值的类型也是 void *。这表明可以用 void * 向新线程传递任意类型的数据,新线程完成时也可返回任意类型的数据。那如何向线程传递一个任意参数?很简单。只要利用 pthread_create() 中的第四个参数。本例中,因为没有必要将任何数据传给微不足道的 thread_function(),所以将第四个参数设为 NULL。
  
  您也许已推测到,在 pthread_create() 成功返回之后,程序将包含两个线程。等一等,两个线程?我们不是只创建了一个线程吗?不错,我们只创建了一个进程。但是主程序同样也是一个线程。可以这样理解:假如编写的程序根本没有使用 POSIX 线程,则该程序是单线程的(这个单线程称为“主”线程)。创建一个新线程之后程序总共就有两个线程了。
  
  我想此时您至少有两个重要问题。第一个问题,新线程创建之后主线程如何运行。答案,主线程按顺序继续执行下一行程序(本例中执行 "if (pthread_join(...))")。第二个问题,新线程结束时如何处理。答案,新线程先停止,然后作为其清理过程的一部分,等待与另一个线程合并或“连接”。
  
  现在,来看一下 pthread_join()。正如 pthread_create() 将一个线程拆分为两个, pthread_join() 将两个线程合并为一个线程。pthread_join() 的第一个参数是 tid mythread。第二个参数是指向 void 指针的指针。假如 void 指针不为 NULL,pthread_join 将线程的 void * 返回值放置在指定的位置上。由于我们不必理会 thread_function() 的返回值,所以将其设为 NULL.
  
  您会注重到 thread_function() 花了 20 秒才完成。在 thread_function() 结束很久之前,主线程就已经调用了 pthread_join()。假如发生这种情况,主线程将中断(转向睡眠)然后等待 thread_function() 完成。当 thread_function() 完成后, pthread_join() 将返回。这时程序又只有一个主线程。当程序退出时,所有新线程已经使用 pthread_join() 合并了。这就是应该如何处理在程序中创建的每个新线程的过程。假如没有合并一个新线程,则它仍然对系统的最大线程数限制不利。这意味着假如未对线程做正确的清理,最终会导致 pthread_create() 调用失败。
  
  
  无父,无子
  假如使用过 fork() 系统调用,可能熟悉父进程和子进程的概念。当用 fork() 创建另一个新进程时,新进程是子进程,原始进程是父进程。这创建了可能非常有用的层次关系,尤其是等待子进程终止时。例如,waitpid() 函数让当前进程等待所有子进程终

来源:https://www.tulaoshi.com/n/20160219/1601292.html

延伸阅读
标签: flash教程
第 1 章: 代码格式及规范 Actions cript 的每行语句都以分号 ";" 结束. 不同于 BASIC 语言, Actions cript 语句同 C++, Java, Pascal 一样允许分多行书写, 即允许将一条很的长语句分割成两个或更多代码行, 只要在结尾有个分号就行了. 允许语句分行书写的唯一缺点是(至少对许多熟悉 BASIC 的人而言): 语句末尾不能忘记加分号. 语句分行唯一的限制...
HTML指令详解 结构 <html> <head> <title>标题<title> </head> <body>..........文件内容.......... </body> </html> 1.文件标题 <title>..........</title> 2.文件更新--<meta> 【1】10秒后自动更新一次 <meta http-equiv="refresh" content=10> 【2】10秒後自动连结到另一文件 <meta htt...
首先阐述什么是同步,不同步有什么问题,然后讨论可以采取哪些措施控制同步,接下来我们会仿照回顾网络通信时那样,构建一个服务器端的“线程池”,JDK为我们提供了一个很大的concurrent工具包,最后我们会对里面的内容进行探索。 为什么要线程同步? 说到线程同步,大部分情况下, 我们是在针对“ 单对象多线程 ”的情况进行讨论,一般会...
wait(),notify(),notifyAll() 不属于 Thread类 ,而是属于 Object基础类 ,也就是说每个对象都有 wait(),notify(),notifyAll()的功能 .因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。 wait导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或被其他线程中断。wait只能由持有对像锁...
摘要 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD中(Specs and Strategy, Specifications, Windows NT File Format Specifications),但是它非常之晦涩。 !-- frame contents -- !-- /frame contents -- 然而这一的文档并未提供足够的信息,所以开发者们无...

经验教程

729

收藏

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