新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → 使用pthreads[转帖] 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 11269 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 使用pthreads[转帖] 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     zhu_ruixian 帅哥哟,离线,有人找我吗?射手座1983-12-2
      
      
      威望:2
      等级:大二期末(Java考了96分!)
      文章:406
      积分:3471
      门派:W3CHINA.ORG
      注册:2006/3/30

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给zhu_ruixian发送一个短消息 把zhu_ruixian加入好友 查看zhu_ruixian的个人资料 搜索zhu_ruixian在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看zhu_ruixian的博客楼主
    发贴心情 使用pthreads[转帖]

    (pThread是什么就不说了,看到这篇文章不错,共享一下吧!)
    这几天想看一下pThread,但是本人没什么毅力,常常是想学什么,然后找本书,但是一般看完前言和
    第一章就扔了...-_____-|||||。所以这次为了使自己看下去,干脆翻译一下这个Getting Started With POSIX Threads
    好了。
    1.介绍:什么是线程,用来干什么?
            线程常常被叫做轻量级的(lightweight)进程,虽然这个称呼有点过于简单了,但这是一个不错的开始。线程虽然不是UNIX的进程,

    但很相似。为了弄懂他们的区别,我们必须看看UNIX进程、Mach任务和线程的关系。在UNIX中,一个进程包括可执行的程序以及许多的资源,

    象是文件描述符表和地址空间。在Mach中一个任务只包括资源;线程负责所有的执行活动。一个Mach任务可以有几个线程与之相关联,而且所

    有的线程必须与某任务关联。与同一个任务相关联的线程共享该任务的资源。所以本质上一个线程就是一个程序计数器,一个堆栈,和一系列

    寄存器--其余所有的数据结构都属于任务。一个在Mach中的UNIX进程被模拟为只有一个线程的任务。
            因为相比于进程,线程很小,所以如果用cpu的消耗来衡量,线程的创建相对廉价。因为进程要求有它们自身的资源,而线程共享资源

    ,所以线程节省内存。Mach线程赋予程序员开发可同时执行于单cpu和多cpu的机器上并发程序的能力,如果有的话可利用额外的cpu。另外,如

    果程序在单cpu环境下易于阻塞或导致迟滞,比如文件操作活套接字操作,线程可以提升性能。
            在接下来的部分中,我们会讨论一部分POSIX线程标准和它在DEC OSF/1 OS上的执行细节,V3.0.POSIX线程被称作pthreads并且与非

    POSIX的cthreads很相似。
    开始使用pthreads(2)Hello World
    2.Hello World
            既然形式做完了,那我们就开始吧。pthread_create函数创建一个新的线程。它有四个参数,一个线程变量或是线程的持有者,一个

    线程的属性,当线程开始执行时调用的函数,一个该函数的参数。比如:
      pthread_t         a_thread;
      pthread_attr_t    a_thread_attribute;
      void              thread_function(void *argument);
      char              *some_argument;
      
      pthread_create( &a_thread, a_thread_attribute, (void *)&thread_function,
                   (void *) &some_argument);
            一个线程的属性当前只指定了被使用的最小的堆栈大小。以后的线程属性可能会更有趣,但现在大多数的程序只要简单的使用默认的

    就行了,即把pthread_attr_default传入函数。与用UNIX的fork命令创建的进程会与父线程同时执行同一条指令不同,线程在指定的函数中开

    始它们的执行。理由十分简单;如果线程不在别处开始执行,会得到的结果是许多线程用相同的资源执行同一条指令。回想一下每个进程都有

    自己的资源,而线程共享它们。
            既然我们知道了如何去创建线程,我们已为我们的第一个程序做好了准备。让我们设计一个在标准输出上
    打印出"Hello World"的多线程的程序。首先我们需要两个线程变量和一个被新的线程调用并执行的函数。
    我们也必须指定每个线程必须打印出不同的消息。一种方法是把两个单词分成两个不同的字符串并且给每个
    线程一个不同的字符串当作它的"startup(启动)"参数。看一下下面的代码:
      void print_message_function( void *ptr );
      
      main()
      {
         pthread_t thread1, thread2;
         char *message1 = "Hello";
         char *message2 = "World";
         
         pthread_create( &thread1, pthread_attr_default,
                        (void*)&print_message_function, (void*) message1);
         pthread_create(&thread2, pthread_attr_default,
                        (void*)&print_message_function, (void*) message2);
      
         exit(0);
      }
      
      void print_message_function( void *ptr )
      {
         char *message;
         message = (char *) ptr;
         printf("%s ", message);
      }

            注意一下print_message_function 的函数原型和在pthread_create 调用中message参数前的转换。这个程序通过调用pthread_create

    函数并传递启动参数"Hello"创建第一个线程;第二个线程通过"World"参数来创建。当第一个线程开始执行时它从带着"Hello"参数的

    print_message_function开始。它打印出"Hello"然后终止执行。当一个线程离开初始的函数它也终止了,所以第一个线程在打印出"Hello"后

    就终止了。当第二个线程开始执行并打印出"World"后也相应的终止了。虽然这个程序看起来很合理,但它有两个缺点。

            第一个也是最重要的,线程并发的执行。因此不能保证第一个线程比第二个线程线到达printf函数。所以我们可能看到"World Hello"

    而不是"Hello World".还有更微妙的一点。注意被父线程的main函数调用的exit函数。如果父线程在两个子线程调用printf之前调用exit,就不

    会有输出被产生。这是因为exit函数退出了这个进程(释放了任务),因此终止了所有线程。任何线程,附线程或子线程,只要有一个调用了

    exit函数,所有的这个进程的线程都将终止。如果想明确的终止线程应调用pthread_exit函数。

            因此我们的hello world小程序有两个竞争情形。exit调用的竞争和哪个子进程先调用printf的竞争。让我们
    用一个不太安全的方法来调整这些竞争。因为我们希望每个子进程在父进程结束前完成执行,让我们
    在父进程中插入一个延迟,这样会给子进程时间去执行printf。为了保证第一个子进程比第二个先执行printf
    ,我们在第二个pthread_create 调用前插入一个延迟。结果代码:
      void print_message_function( void *ptr );
      
      main()
      {
         pthread_t thread1, thread2;
         char *message1 = "Hello";
         char *message2 = "World";
         
         pthread_create( &thread1, pthread_attr_default,
                        (void *) &print_message_function, (void *) message1);
         sleep(10);
         pthread_create(&thread2, pthread_attr_default,
                        (void *) &print_message_function, (void *) message2);
      
         sleep(10);
         exit(0);
      }
      
      void print_message_function( void *ptr )
      {
         char *message;
         message = (char *) ptr;
         printf("%s", message);
         pthread_exit(0);
      }
            这段代码是否符合我们的目标了呢?并不是。依靠时间延迟来执行同步永远不是安全的。因为线程之间的
    紧密耦合而诱使人们用不那么严格的态度去处理同步,但那是应该被避免的。这里的竞争情形与一个分布式的应用程序和一个共享的资源情况

    相同。这里资源就是标准输出而分布式的计算单元就是三个线程。线程一必须比线程而先用ptintf/stdout,而且他们必须在父线程退出之前做


            在我们尝试用延时来同步的另一面,我们又犯了另一个大错。sleep函数与exit函数一样是与整个进程相连
    系的。如果一个线程调用了sleep函数,整个进程将休眠,也就是当进程休眠时所有的线程也休眠了。因此这与我们不调用sleep函数的结果一

    样,只不过程序多运行了20秒。当想延迟一个线程的合适函数是pthread_delay_np(np代表not portable)。比如,延迟一个线程2秒钟:
         struct timespec delay;
         delay.tv_sec = 2;
         delay.tv_nsec = 0;
         pthread_delay_np( &delay );
         
    本节提及的函数:
    pthread_create(), pthread_exit(), and pthread_delay_np().


    开始使用pthreads(3)线程同步
    3.线程的同步
            POSIX提供了两个同步的原语,mutex(互斥)和condition(条件)变量。互斥是可以被用来控制共享变量的访问简单的锁原语。注意,

    对于线程来说,整个地址空间都是共享的,所以所有的东西都可以被当作共享资源。然而,在大多数情况下,线程使用私有的本地变量(在

    pthread_create及连续的函数中制造出来的)单独的工作(理论上),并通过全局变量来把它们的成果合并起来。对于线程都要进行写操作的

    变量的访问必须被控制。
    让我们创建一个readers/writers程序,在这个程序中有一个reader和一个writer通过一个共享的缓存来通信并且通过互斥来控制访问:
      void reader_function(void);
      void writer_function(void);
      
      char buffer;
      int buffer_has_item = 0;
      pthread_mutex_t mutex;
      struct timespec delay;
      
      main()
      {
         pthread_t reader;
      
         delay.tv_sec = 2;
         delay.tv_nsec = 0;
      
         pthread_mutex_init(&mutex, pthread_mutexattr_default);
         pthread_create( &reader, pthread_attr_default, (void*)&reader_function,
                        NULL);
         writer_function();
      }
      
      void writer_function(void)
      {
         while(1)
         {
              pthread_mutex_lock( &mutex );
              if ( buffer_has_item == 0 )
              {
                   buffer = make_new_item();
                   buffer_has_item = 1;
              }
              pthread_mutex_unlock( &mutex );
              pthread_delay_np( &delay );
         }
      }
      
      void reader_function(void)
      {
         while(1)
         {
              pthread_mutex_lock( &mutex );
              if ( buffer_has_item == 1)
              {
                   consume_item( buffer );
                   buffer_has_item = 0;
              }
              pthread_mutex_unlock( &mutex );
              pthread_delay_np( &delay );
         }
      }
           在这个简单的程序中我们假设缓存只能容纳一项,所以它一直是处于两种状态的其中一种,有内容或没有。writer首先锁住互斥变量,

    如果已经被上锁了,那么该线程阻塞直到被解锁,然后查看缓存是否为空。如果缓存为空,它创建一个新的项并设置标记buffer_has_item,因

    此reader会知道现在缓存里有内容。然后解锁互斥变量并延时2秒钟让reader有机会消耗这项内容。这个延时与我们的前一个延时不同,它是用

    来改善程序性能的。如果没有这个延时,writer在释放锁之后可能马上又获得了锁并试图再制造另一项内容。reader很可能没有机会这么快的

    消耗这项内容,所以延时是一个好办法。
            reader的情况也差不多。他获得这个锁,查看是否存在内容,如果有就消耗它。它释放锁并延时一小段时间来给writer机会去制造新

    的内容。在这个例子中reader和writer会一直执行下去,制造和消耗内容。如果一个互斥变量不再被需要,可以通过pthread_mutex_destroy

    (&mutex)来释放。观察在互斥变量的初始化函数中,我们使用被要求的pthread_ mutexattr_default作为互斥变量的属性。在OSF/1中,互斥变

    量属性没什么作用,所以强烈推荐使用默认值。
            适当的使用户斥变量保证消除了竞争情形。但是,互斥变量本身十分的弱,因为它只有两个状态:被锁和未被锁。POSIX的条件变量通

    过允许一个线程阻塞而去等待另一个线程的信号来补充互斥变量。当这个信号被接受到了,被阻塞的信号被唤醒去尝试获得一个与之像关的互

    斥变量。因此信号和互斥可以被合并起来消除readers/writers带来的自旋锁问题。我们已经设计了一个通过pthreads的mutex和condition实现

    的简单的整数信号量并且今后会在那个环境中讨论同步的问题。信号量的代码可以在附录A中找到,关于条件变量的细节问题
    可以在帮助(man)页中找到。

    本节提到的函数:
    pthread_mutex_init(), pthread_mutex_lock(),
    pthread_mutex_unlock(), and pthread_mutex_destroy().

    开始使用pthreads(4)用信号量协调事件
    4.用信号量协调事件
    让我们用信号量来重新回顾一下readers/writes程序。我们会用最健壮的整数信号量来替代互斥原语来消除自旋锁的问题。信号量的操作是

    semaphore_up, semaphore_down, semaphore_init, semaphore_destroy,和 semaphore_decrement。semaphore_up, semaphore_down遵循传统

    的信号量语法-- 如果信号量的值小于或等于0,semaphore_down试线程阻塞,semaphore_up增加信号量的值。semaphore_init必须在使用信号

    量前被调用并且所有的信号量都应被初始化为值1。semaphore_destroy函数释放不再使用的信号量。所有的函数使用一个指向信号量实例的参

    数。semaphore_decrement是一个非阻塞的函数,它减少信号量的值。它允许线程在初始化过程中把信号量的值减为负的。我们将看一个使用

    semaphore_decrement的readers/writers的程序:
      void reader_function(void);
      void writer_function(void);
      
      char buffer;
      Semaphore writers_turn;
      Semaphore readers_turn;
      
      main()
      {
         pthread_t reader;
      
         semaphore_init( &readers_turn );
         semaphore_init( &writers_turn );
      
         /* writer must go first */
         semaphore_down( &readers_turn );
      
         pthread_create( &reader, pthread_attr_default,
                        (void *)&reader_function, NULL);
         writer_function();
      }
      
      void writer_function(void)
      {
         while(1)
         {
              semaphore_down( &writers_turn );
              buffer = make_new_item();
              semaphore_up( &readers_turn );
         }
      }
      
      void reader_function(void)
      {
         while(1)
         {
              semaphore_down( &readers_turn );
              consume_item( buffer );
              semaphore_up( &writers_turn );
         }
      }

    这个例子仍然没有完全发挥通用整数信号量的能力。让我们再回顾一下第二节的Hello World程序并把它的竞争情形用整数信号量来消除。
    void print_message_function( void *ptr );

    Semaphore child_counter;
    Semaphore worlds_turn;

    main()
    {
         pthread_t thread1, thread2;
         char *message1 = "Hello";
         char *message2 = "World";

         semaphore_init( &child_counter );
         semaphore_init( &worlds_turn );

         semaphore_down( &worlds_turn ); /* world goes second */
         
         semaphore_decrement( &child_counter ); /* value now 0 */
         semaphore_decrement( &child_counter ); /* value now -1 */
         /*
          * child_counter now must be up-ed 2 times for a thread blocked on it
          * to be released
          *
          */
         

         pthread_create( &thread1, pthread_attr_default,
                        (void *) &print_message_function, (void *) message1);

         semaphore_down( &worlds_turn );

         pthread_create(&thread2, pthread_attr_default,
                        (void *) &print_message_function, (void *) message2);

         semaphore_down( &child_counter );

         /* not really necessary to destroy since we are exiting anyway */
         semaphore_destroy ( &child_counter );
         semaphore_destroy ( &worlds_turn );
         exit(0);
    }

    void print_message_function( void *ptr )
    {
         char *message;
         message = (char *) ptr;
         printf("%s ", message);
         fflush(stdout);
         semaphore_up( &worlds_turn );
         semaphore_up( &child_counter );
         pthread_exit(0);
    }

    在这个hello world的版本中没有竞争情形,单词以适当的顺序打印出来,读者们该满意了。child_counter信号量迫使父线程在两个子线程执

    行完printf和semaphore_up( &child_counter )之前都被阻塞。

    这节提到了函数:
    semaphore_init(), semaphore_up(), semaphore_down(),
    semaphore_destroy(), and semaphore_decrement().

    开始使用pthreads(5)编程方法
    5.编程方法
    为了编译pthreads,你必须包含pthreads头文件,#include并且链接到pthread库。例如,cc hello_world.c -o hello_world -lpthreads(在

    alpha上,你还应包含 -lc_r)。为了使用信号量库,你也应包含它的头文件并且链接到目标文件或者库。DEC pthreads是建立在POSIX IV 线程

    标准的基础上的,而不是在POSIX VIII线程基础上的。pthread_join函数允许一个线程去等待另一个线程的结束。这个函数可以被用在Hello

    World程序中来替代semaphore_up()/semaphore_down()的信号量操作,DEC对pthread_join函数的实现当指定的线程目标不再存在时将变得不可

    靠。例如,在下面的代码中,如果some_thread不再存在,pthread_join不会返回而是会导致错误。
         pthread_t some_thread;
         void *exit_status;
         pthread_join( some_thread, &exit_status );

    另一些奇怪的错误会在这个线程的函数外发生。当这些错误很说发生并且离的较远,一些库做了"单进程"的假设。例如,我们已经历过了在使

    用有缓存的输入输出流函数fread和fwrite发生的间歇性的困难,这只能归因于竞争情形。在错误的问题上,虽然我们不检查线程相关函数的返

    回值,返回值必须始终被检查。几乎所有的pthreads相关的函数如果返回-1这代表有错误。比如:
         pthread_t some_thread;
         if ( pthread_create( &some_thread, ... ) == -1 )
         {
              perror("Thread creation error");
              exit(1);
         }
    信号量库会打印出信息并且在有错误的情况下退出。一些有用的函数没有在例子中提到:
    pthread_yield();         Informs the scheduler that the thread is willing to yield its quantum, requires
                             no arguments.

    pthread_t me;
    me = pthread_self();     Allows a pthread to obtain its own identifier

    pthread_t thread;
    pthread_detach(thread);  Informs the library that the threads exit status will not be needed by
                             subsequent pthread_join calls resulting in better threads performance.


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    为什么总是索取的人多,奉献的人少...

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/10/21 19:02:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客2
    发贴心情 
    支持一下

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/10/23 14:49:00
     
     everlasting_188 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:4
      积分:72
      门派:XML.ORG.CN
      注册:2008/12/9

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给everlasting_188发送一个短消息 把everlasting_188加入好友 查看everlasting_188的个人资料 搜索everlasting_188在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看everlasting_188的博客3
    发贴心情 
    非常感谢!!!!!
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2008/12/19 10:56:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/28 14:14:59

    本主题贴数3,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    1,082.031ms