??xml version="1.0" encoding="utf-8" standalone="yes"?>99久久成人18免费网站,日韩乱码人妻无码中文字幕久久,国产成人无码久久久精品一http://m.shnenglu.com/zjl-1026-2001/archive/2010/03/18/109959.html沙漠里的沙漠里的Thu, 18 Mar 2010 01:54:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/18/109959.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/109959.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/18/109959.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/109959.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/109959.html阅读全文

沙漠里的 2010-03-18 09:54 发表评论
]]>
[转蝲]POSIX U程详解-W?部分http://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109419.html沙漠里的沙漠里的Thu, 11 Mar 2010 03:54:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109419.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/109419.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109419.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/109419.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/109419.html
本文?POSIX U程三部曲系列的最后一部分QDaniel 详l讨论如何用条件变量。条件变量是 POSIX U程l构Q可以让您在遇到某些条g?#8220;唤醒”U程。可以将它们看作是一U线E安全的信号发送。Daniel 使用目前您所学到的知识实C一个多U程工作l应用程序,本文围l着q一CZ而进行讨论?/blockquote>

条g变量详解

?上一文?/font>l束Ӟ我描qC一个比较特D的NQ如果线E正在等待某个特定条件发生,它应该如何处理这U情况?它可以重复对互斥对象锁定和解锁,每次都会查共享数据结构,以查找某个倹{但q是在浪Ҏ间和资源Q而且q种J忙查询的效率非怽。解册个问题的最x法是使用 pthread_cond_wait() 调用来等待特D条件发生?

了解 pthread_cond_wait() 的作用非帔R?-- 它是 POSIX U程信号发送系l的核心Q也是最难以理解的部分?/p>

首先Q让我们考虑以下情况Q线Eؓ查看已链接列表而锁定了互斥对象Q然而该列表恰y是空的。这一特定U程什么也q不?-- 其设计意图是从列表中除去节点Q但是现在却没有节点。因此,它只能:

锁定互斥对象ӞU程调?pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂Q因此我们每ơ只执行它的一个操作?/p>

pthread_cond_wait() 所做的W一件事是同时对互斥对象解锁(于是其它U程可以修改已链接列表)Qƈ{待条g mycond 发生Q这样当 pthread_cond_wait() 接收到另一个线E的“信号”Ӟ它将苏醒Q。现在互斥对象已被解锁,其它U程可以讉K和修改已链接列表Q可能还会添加项?/p>

此时Qpthread_cond_wait() 调用q未q回。对互斥对象解锁会立卛_生,但等待条?mycond 通常是一个阻塞操作,q意味着U程睡眠,在它苏醒之前不会消?CPU 周期。这正是我们期待发生的情c线E将一直睡眠,直到特定条g发生Q在q期间不会发生Q何浪?CPU 旉的繁忙查询。从U程的角度来看,它只是在{待 pthread_cond_wait() 调用q回?/p>

现在l箋说明Q假讑֏一个线E(UC“2 LE?#8221;Q锁定了 mymutex q对已链接列表添加了一V在对互斥对象解锁之后,2 LE会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后Q? LE将使所有等?mycond 条g变量的线E立卌醒。这意味着W一个线E(仍处?pthread_cond_wait() 调用中)现在苏醒?/p>

现在Q看一下第一个线E发生了什么。您可能会认为在 2 LE调?pthread_cond_broadcast(&mymutex) 之后Q? LE的 pthread_cond_wait() 会立卌回。不是那P实际上,pthread_cond_wait() 执行最后一个操作:重新锁定 mymutex。一?pthread_cond_wait() 锁定了互斥对象,那么它将q回q允?1 LEl执行。那Ӟ它可以马上检查列表,查看它所感兴的更改?/p>

停止q回!

那个q程非常复杂Q因此让我们先来回顾一下。第一个线E首先调用:

    pthread_mutex_lock(&mymutex);
            

然后Q它查了列表。没有找到感兴趣的东西,于是它调用:

    pthread_cond_wait(&mycond, &mymutex);
            

然后Qpthread_cond_wait() 调用在返回前执行许多操作Q?/p>
            pthread_mutex_unlock(&mymutex);
            

它对 mymutex 解锁Q然后进入睡眠状态,{待 mycond 以接?POSIX U程“信号”。一旦接收到“信号”Q加引号是因为我们ƈ不是在讨Zl的 UNIX 信号Q而是来自 pthread_cond_signal() ?pthread_cond_broadcast() 调用的信PQ它׃苏醒。但 pthread_cond_wait() 没有立即q回 -- 它还要做一件事Q重新锁?mutexQ?/p>
            pthread_mutex_lock(&mymutex);
            

pthread_cond_wait() 知道我们在查?mymutex “背后”的变化,因此它l操作,为我们锁定互斥对象,然后才返回?/p>

pthread_cond_wait() 测?/span>

现在已回了 pthread_cond_wait() 调用Q您应该了解了它的工作方式。应该能够叙q?pthread_cond_wait() 依次执行的所有操作。尝试一下。如果理解了 pthread_cond_wait()Q其余部分就相当ҎQ因此请重新阅读以上部分Q直到记住ؓ止。好Q读完之后,能否告诉我在调用 pthread_cond_wait() ??/em>Q互斥对象必d于什么状态?pthread_cond_wait() 调用q回之后Q互斥对象处于什么状态?q两个问题的{案都是“锁定”。既然已l完全理解了 pthread_cond_wait() 调用Q现在来l箋研究更简单的东西 -- 初始化和真正的发送信号和q播q程。到那时Q我们将会对包含了多U程工作队列?C 代码了如指掌?



初始化和清除

条g变量是一个需要初始化的真实数据结构。以下就初始化的Ҏ。首先,定义或分配一个条件变量,如下所C:

    pthread_cond_t mycond;
            

然后Q调用以下函数进行初始化Q?/p>
    pthread_cond_init(&mycond,NULL);
            

瞧,初始化完成了Q在释放或废弃条件变量之前,需要毁坏它Q如下所C:

    pthread_cond_destroy(&mycond);
            

很简单吧。接着讨论 pthread_cond_wait() 调用?/p>

{待

一旦初始化了互斥对象和条g变量Q就可以{待某个条gQ如下所C:

    pthread_cond_wait(&mycond, &mymutex);
            

h意,代码在逻辑上应该包?mycond ?mymutex。一个特定条件只能有一个互斥对象,而且条g变量应该表示互斥数据“内部”的一U特D的条g更改。一个互斥对象可以用许多条g变量Q例如,cond_empty、cond_full、cond_cleanupQ,但每个条件变量只能有一个互斥对象?/p>

发送信号和q播

对于发送信号和q播Q需要注意一炏V如果线E更Ҏ些共享数据,而且它想要唤醒所有正在等待的U程Q则应?pthread_cond_broadcast 调用Q如下所C:

    pthread_cond_broadcast(&mycond);
            

在某些情况下Q活动线E只需要唤醒第一个正在睡眠的U程。假设您只对队列d了一个工作作业。那么只需要唤醒一个工作程序线E(再唤醒其它线E是不礼貌的Q)Q?/p>
    pthread_cond_signal(&mycond);
            

此函数只唤醒一个线E。如?POSIX U程标准允许指定一个整敎ͼ可以让您唤醒一定数量的正在睡眠的线E,那就更完了。但是很可惜Q我没有被邀请参加会议?/p>

工作l?/span>

我将演示如何创徏多线E工作组。在q个Ҏ中,我们创徏了许多工作程序线E。每个线E都会检?wqQ?#8220;工作队列”Q,查看是否有需要完成的工作。如果有需要完成的工作Q那么线E将从队列中除去一个节点,执行q些特定工作Q然后等待新的工作到达?/p>

与此同时Q主U程负责创徏q些工作E序U程、将工作d到队列,然后在它退出时攉所有工作程序线E。您会遇到许多 C 代码Q好好准备吧Q?/p>

队列

需要队列是Z两个原因。首先,需要队列来保存工作作业。还需要可用于跟踪已终止线E的数据l构。还记得前几文章(请参阅本文结֤?参考资?/font>Q中Q我曾提到过需要用带有特定进E标识的 pthread_join 吗?使用“清除队列”Q称?"cq"Q可以解x法等?M已终止线E的问题Q稍后将详细讨论q个问题Q。以下是标准队列代码。将此代码保存到文g queue.h ?queue.cQ?


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;
            typedef struct queue {
            node *head, *tail;
            } queue;
            void queue_init(queue *myroot);
            void queue_put(queue *myroot, node *mynode);
            node *queue_get(queue *myroot);
            



queue.c
/* queue.c
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            **
            ** This set of queue functions was originally thread-aware.  I
            ** redesigned the code to make this set of queue routines
            ** thread-ignorant (just a generic, boring yet very fast set of queue
            ** routines).  Why the change?  Because it makes more sense to have
            ** the thread support as an optional add-on.  Consider a situation
            ** where you want to add 5 nodes to the queue.  With the
            ** thread-enabled version, each call to queue_put() would
            ** automatically lock and unlock the queue mutex 5 times -- that's a
            ** lot of unnecessary overhead.  However, by moving the thread stuff
            ** out of the queue routines, the caller can lock the mutex once at
            ** the beginning, then insert 5 items, and then unlock at the end.
            ** Moving the lock/unlock code out of the queue functions allows for
            ** optimizations that aren't possible otherwise.  It also makes this
            ** code useful for non-threaded applications.
            **
            ** We can easily thread-enable this data structure by using the
            ** data_control type defined in control.c and control.h.  */
            #include <stdio.h>
            #include "queue.h"
            void queue_init(queue *myroot) {
            myroot->head=NULL;
            myroot->tail=NULL;
            }
            void queue_put(queue *myroot,node *mynode) {
            mynode->next=NULL;
            if (myroot->tail!=NULL)
            myroot->tail->next=mynode;
            myroot->tail=mynode;
            if (myroot->:head==NULL)
            myroot->head=mynode;
            }
            node *queue_get(queue *myroot) {
            //get from root
            node *mynode;
            mynode=myroot->head;
            if (myroot->head!=NULL)
            myroot->head=myroot->head->next;
            return mynode;
            }
            

data_control 代码

我编写的q不是线E安全的队列例程Q事实上我创Z一?#8220;数据包装”?#8220;控制”l构Q它可以是Q何线E支持的数据l构。看一?control.hQ?/p>
control.h
#include
            typedef struct data_control {
            pthread_mutex_t mutex;
            pthread_cond_t cond;
            int active;
            } data_control;
            

现在您看C data_control l构定义Q以下是它的视觉表示Q?/p>
所使用?data_control l构

囑փ中的锁代表互斥对象,它允许对数据l构q行互斥讉K。黄色的星代表条件变量,它可以睡眠,直到所讨论的数据结构改变ؓ止。on/off 开兌C整?"active"Q它告诉U程此数据是否是zd的。在代码中,我用整?active 作ؓ标志Q告诉工作队列何时应该关闭。以下是 control.cQ?/p>
control.c
/* control.c
            ** Copyright 2000 Daniel Robbins, Gentoo Technologies, Inc.
            ** Author: Daniel Robbins
            ** Date: 16 Jun 2000
            **
            ** These routines provide an easy way to make any type of
            ** data-structure thread-aware.  Simply associate a data_control
            ** structure with the data structure (by creating a new struct, for
            ** example).  Then, simply lock and unlock the mutex, or
            ** wait/signal/broadcast on the condition variable in the data_control
            ** structure as needed.
            **
            ** data_control structs contain an int called "active".  This int is
            ** intended to be used for a specific kind of multithreaded design,
            ** where each thread checks the state of "active" every time it locks
            ** the mutex.  If active is 0, the thread knows that instead of doing
            ** its normal routine, it should stop itself.  If active is 1, it
            ** should continue as normal.  So, by setting active to 0, a
            ** controlling thread can easily inform a thread work crew to shut
            ** down instead of processing new jobs.  Use the control_activate()
            ** and control_deactivate() functions, which will also broadcast on
            ** the data_control struct's condition variable, so that all threads
            ** stuck in pthread_cond_wait() will wake up, have an opportunity to
            ** notice the change, and then terminate.
            */
            #include "control.h"
            int control_init(data_control *mycontrol) {
            int mystatus;
            if (pthread_mutex_init(&(mycontrol->mutex),NULL))
            return 1;
            if (pthread_cond_init(&(mycontrol->cond),NULL))
            return 1;
            mycontrol->active=0;
            return 0;
            }
            int control_destroy(data_control *mycontrol) {
            int mystatus;
            if (pthread_cond_destroy(&(mycontrol->cond)))
            return 1;
            if (pthread_cond_destroy(&(mycontrol->cond)))
            return 1;
            mycontrol->active=0;
            return 0;
            }
            int control_activate(data_control *mycontrol) {
            int mystatus;
            if (pthread_mutex_lock(&(mycontrol->mutex)))
            return 0;
            mycontrol->active=1;
            pthread_mutex_unlock(&(mycontrol->mutex));
            pthread_cond_broadcast(&(mycontrol->cond));
            return 1;
            }
            int control_deactivate(data_control *mycontrol) {
            int mystatus;
            if (pthread_mutex_lock(&(mycontrol->mutex)))
            return 0;
            mycontrol->active=0;
            pthread_mutex_unlock(&(mycontrol->mutex));
            pthread_cond_broadcast(&(mycontrol->cond));
            return 1;
            }
            

调试旉

在开始调试之前,q需要一个文件。以下是 dbug.hQ?/p>
dbug.h
#define dabort() \
            {  printf("Aborting at line %d in source file %s\n",__LINE__,__FILE__); abort(); }
            

此代码用于处理工作组代码中的不可U正错误?/p>

工作l代?/span>

说到工作l代码,以下是Q?/p>
workcrew.c
#include <stdio.h>
            #include <stdlib.h>
            #include "control.h"
            #include "queue.h"
            #include "dbug.h"
            /* the work_queue holds tasks for the various threads to complete. */
            struct work_queue {
            data_control control;
            queue work;
            } wq;
            /* I added a job number to the work node.  Normally, the work node
            would contain additional data that needed to be processed. */
            typedef struct work_node {
            struct node *next;
            int jobnum;
            } wnode;
            /* the cleanup queue holds stopped threads.  Before a thread
            terminates, it adds itself to this list.  Since the main thread is
            waiting for changes in this list, it will then wake up and clean up
            the newly terminated thread. */
            struct cleanup_queue {
            data_control control;
            queue cleanup;
            } cq;
            /* I added a thread number (for debugging/instructional purposes) and
            a thread id to the cleanup node.  The cleanup node gets passed to
            the new thread on startup, and just before the thread stops, it
            attaches the cleanup node to the cleanup queue.  The main thread
            monitors the cleanup queue and is the one that performs the
            necessary cleanup. */
            typedef struct cleanup_node {
            struct node *next;
            int threadnum;
            pthread_t tid;
            } cnode;
            void *threadfunc(void *myarg) {
            wnode *mywork;
            cnode *mynode;
            mynode=(cnode *) myarg;
            pthread_mutex_lock(&wq.control.mutex);
            while (wq.control.active) {
            while (wq.work.head==NULL && wq.control.active) {
            pthread_cond_wait(&wq.control.cond, &wq.control.mutex);
            }
            if (!wq.control.active)
            break;
            //we got something!
            mywork=(wnode *) queue_get(&wq.work);
            pthread_mutex_unlock(&wq.control.mutex);
            //perform processing...
            printf("Thread number %d processing job %d\n",mynode->threadnum,mywork->jobnum);
            free(mywork);
            pthread_mutex_lock(&wq.control.mutex);
            }
            pthread_mutex_unlock(&wq.control.mutex);
            pthread_mutex_lock(&cq.control.mutex);
            queue_put(&cq.cleanup,(node *) mynode);
            pthread_mutex_unlock(&cq.control.mutex);
            pthread_cond_signal(&cq.control.cond);
            printf("thread %d shutting down...\n",mynode->threadnum);
            return NULL;
            }
            #define NUM_WORKERS 4
            int numthreads;
            void join_threads(void) {
            cnode *curnode;
            printf("joining threads...\n");
            while (numthreads) {
            pthread_mutex_lock(&cq.control.mutex);
            /* below, we sleep until there really is a new cleanup node.  This
            takes care of any false wakeups... even if we break out of
            pthread_cond_wait(), we don't make any assumptions that the
            condition we were waiting for is true.  */
            while (cq.cleanup.head==NULL) {
            pthread_cond_wait(&cq.control.cond,&cq.control.mutex);
            }
            /* at this point, we hold the mutex and there is an item in the
            list that we need to process.  First, we remove the node from
            the queue.  Then, we call pthread_join() on the tid stored in
            the node.  When pthread_join() returns, we have cleaned up
            after a thread.  Only then do we free() the node, decrement the
            number of additional threads we need to wait for and repeat the
            entire process, if necessary */
            curnode = (cnode *) queue_get(&cq.cleanup);
            pthread_mutex_unlock(&cq.control.mutex);
            pthread_join(curnode->tid,NULL);
            printf("joined with thread %d\n",curnode->threadnum);
            free(curnode);
            numthreads--;
            }
            }
            int create_threads(void) {
            int x;
            cnode *curnode;
            for (x=0; x<NUM_WORKERS; x++) {
            curnode=malloc(sizeof(cnode));
            if (!curnode)
            return 1;
            curnode->threadnum=x;
            if (pthread_create(&curnode->tid, NULL, threadfunc, (void *) curnode))
            return 1;
            printf("created thread %d\n",x);
            numthreads++;
            }
            return 0;
            }
            void initialize_structs(void) {
            numthreads=0;
            if (control_init(&wq.control))
            dabort();
            queue_init(&wq.work);
            if (control_init(&cq.control)) {
            control_destroy(&wq.control);
            dabort();
            }
            queue_init(&wq.work);
            control_activate(&wq.control);
            }
            void cleanup_structs(void) {
            control_destroy(&cq.control);
            control_destroy(&wq.control);
            }
            int main(void) {
            int x;
            wnode *mywork;
            initialize_structs();
            /* CREATION */
            if (create_threads()) {
            printf("Error starting threads... cleaning up.\n");
            join_threads();
            dabort();
            }
            pthread_mutex_lock(&wq.control.mutex);
            for (x=0; x<16000; x++) {
            mywork=malloc(sizeof(wnode));
            if (!mywork) {
            printf("ouch! can't malloc!\n");
            break;
            }
            mywork->jobnum=x;
            queue_put(&wq.work,(node *) mywork);
            }
            pthread_mutex_unlock(&wq.control.mutex);
            pthread_cond_broadcast(&wq.control.cond);
            printf("sleeping...\n");
            sleep(2);
            printf("deactivating work queue...\n");
            control_deactivate(&wq.control);
            /* CLEANUP  */
            join_threads();
            cleanup_structs();
            }
            

代码初排

现在来快速初排代码。定义的W一个结构称?"wq"Q它包含?data_control 和队列头。data_control l构用于仲裁Ҏ个队列的讉KQ包括队列中的节炏V下一步工作是定义实际的工作节炏V要使代码符合本文中的示例,此处所包含的都是作业号?/p>

接着Q创建清除队列。注释说明了它的工作方式。好Q现在让我们跌 threadfunc()、join_threads()、create_threads() ?initialize_structs() 调用Q直接蟩?main()。所做的W一件事是初始化结?-- q包括初始化 data_controls 和队列,以及Ȁzd作队列?/p>

有关清除的注意事?/span>

现在初始化线E。如果看一?create_threads() 调用Q似乎一切正?-- 除了一件事。请注意Q我们正在分配清除节点,以及初始化它的线E号?TID lg。我们还清除节点作为初始自变量传递给每一个新的工作程序线E。ؓ什么这样做Q?/p>

因ؓ当某个工作程序线E退出时Q它会将其清除节点连接到清除队列Q然后终止。那ӞȝE会在清除队列中到q个节点Q利用条件变量)Qƈ这个节点移出队列。因?TIDQ线E标识)存储在清除节点中Q所以主U程可以切知道哪个U程已终止了。然后,ȝE将调用 pthread_join(tid)Qƈ联接适当的工作程序线E。如果没有做记录Q那么主U程需要按L序联接工作E序U程Q可能是按它们的创徏序。由于线E不一定按此顺序终止,那么ȝE可能会在已l联接了十个U程Ӟ{待联接另一个线E。您能理解这U设计决{是如何使关闭代码加速的吗(其在用几百个工作E序U程的情况下Q?


创徏工作

我们已启动了工作E序U程Q它们已l完成了执行 threadfunc()Q稍后将讨论此函敎ͼQ现在主U程开始将工作节点插入工作队列。首先,它锁?wq 的控制互斥对象,然后分配 16000 个工作包Q将它们逐个插入队列。完成之后,调?pthread_cond_broadcast()Q于是所有正在睡眠的U程会被唤醒Qƈ开始执行工作。此ӞȝE将睡眠两秒钟,然后释放工作队列Qƈ通知工作E序U程l止zd。接着Q主U程会调?join_threads() 函数来清除所有工作程序线E?/p>

threadfunc()

现在来讨?threadfunc()Q这是所有工作程序线E都要执行的代码。当工作E序U程启动Ӟ它会立即锁定工作队列互斥对象Q获取一个工作节点(如果有的话)Q然后对它进行处理。如果没有工作,则调?pthread_cond_wait()。您会注意到q个调用在一个非常紧凑的 while() 循环中,q是非常重要的。当?pthread_cond_wait() 调用中苏醒时Q决不能认ؓ条g肯定发生?-- ?可能发生了,也可能没有发生。如果发生了q种情况Q即错误地唤醒了U程Q而列表是I的Q那?while 循环再ơ调?pthread_cond_wait()?

如果有一个工作节点,那么我们只打印它的作业号Q释攑֮q出。然而,实际代码会执行一些更实质性的操作。在 while() 循环l尾Q我们锁定了互斥对象Q以便检?active 变量Q以及在循环剙查新的工作节炏V如果执行完此代码,׃发现如果 wq.control.active ?0Qwhile 循环׃l止Qƈ会执?threadfunc() l尾处的清除代码?/p>

工作E序U程的清除代码部仉常有。首先,׃ pthread_cond_wait() q回了锁定的互斥对象Q它会对 work_queue 解锁。然后,它锁定清除队列,d清除代码Q包含了 TIDQ主U程用此 TID 来调?pthread_join()Q,然后再对清除队列解锁。此后,它发信号l所?cq {待?(pthread_cond_signal(&cq.control.cond))Q于是主U程q道有一个待处理的新节点。我们不使用 pthread_cond_broadcast()Q因为没有这个必?-- 只有一个线E(ȝE)在等待清除队列中的新节点。当它调?join_threads() Ӟ工作E序U程打印关闭消息,然后l止Q等待主U程发出?pthread_join() 调用?/p>

join_threads()

如果要查看关于如何用条件变量的单示例,请参?join_threads() 函数。如果还有工作程序线E,join_threads() 会一直执行,{待清除队列中新的清除节炏V如果有新节点,我们会将此节点移出队列、对清除队列解锁Q从而工作E序可以d清除节点Q、联接新的工作程序线E(使用存储在清除节点中?TIDQ、释放清除节炏V减?#8220;现有”U程的数量,然后l箋?/p>

l束?/span>

现在已经C“POSIX U程详解”pd的尾壎ͼ希望您已l准备好开始将多线E代码添加到您自q应用E序中。有兌l信息,请参?参考资?/font>部分Q这部分内容q包含了本文中用的所有源码的 tar 文g。下一个系列中再见Q?


参考资?



关于作?/span>

 

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQGentoo 目的总设计师QMacMillan 出版书籍的撰E作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, ?Samba Unleashed。Daniel 自二q起就与计机某些领域l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和新出生的女?Hadassah 一起共度时光。可通过 drobbins@gentoo.org?Daniel 联系?

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQGentoo 目的总设计师QMacMillan 出版书籍的撰E作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, ?Samba Unleashed。Daniel 自二q起就与计机某些领域l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和新出生的女?Hadassah 一起共度时光。可通过 drobbins@gentoo.org?Daniel 联系?

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQGentoo 目的总设计师QMacMillan 出版书籍的撰E作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, ?Samba Unleashed。Daniel 自二q起就与计机某些领域l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和新出生的女?Hadassah 一起共度时光。可通过 drobbins@gentoo.org?Daniel 联系?

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQGentoo 目的总设计师QMacMillan 出版书籍的撰E作者,他的著作有: Caldera OpenLinux Unleashed, SuSE Linux Unleashed, ?Samba Unleashed。Daniel 自二q起就与计机某些领域l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和新出生的女?Hadassah 一起共度时光。可通过 drobbins@gentoo.org?Daniel 联系?




沙漠里的 2010-03-11 11:54 发表评论
]]>[转蝲]POSIX U程详解-W?部分http://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109417.html沙漠里的沙漠里的Thu, 11 Mar 2010 03:46:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109417.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/109417.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109417.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/109417.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/109417.htmlhttp://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/

POSIX U程是提高代码响应和性能的有力手Dc在此三部分pd文章的第二篇中,Daniel Robbins 说明,如何使用被称Z斥对象的灵y玩意,来保护线E代码中׃n数据l构的完整性?/blockquote>

互斥我吧Q?/span>

?前一文章中 Q谈C会导致异常结果的U程代码。两个线E分别对同一个全局变量q行了二十次加一。变量的值最后应该是 40Q但最l值却?21。这是怎么回事呢?因ؓ一个线E不停地“取消”了另一个线E执行的加一操作Q所以生这个问题。现在让我们来查看改正后的代码,它?互斥对象(mutex)来解册问题Q?


thread3.c

#include <pthread.h>
            #include <stdlib.h>
            #include <unistd.h>
            #include <stdio.h>
            int myglobal;
            pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
            void *thread_function(void *arg) {
            int i,j;
            for ( i=0; i<20; i++) {
            pthread_mutex_lock(&mymutex);
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);
            myglobal=j;
            pthread_mutex_unlock(&mymutex);
            }
            return NULL;
            }
            int main(void) {
            pthread_t mythread;
            int i;
            if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
            printf("error creating thread.");
            abort();
            }
            for ( i=0; i<20; i++) {
            pthread_mutex_lock(&mymutex);
            myglobal=myglobal+1;
            pthread_mutex_unlock(&mymutex);
            printf("o");
            fflush(stdout);
            sleep(1);
            }
            if ( pthread_join ( mythread, NULL ) ) {
            printf("error joining thread.");
            abort();
            }
            printf("\nmyglobal equals %d\n",myglobal);
            exit(0);
            }
            

解读一?/font>

如果这D代码与 前一文?/font> 中给出的版本作一个比较,׃注意到增加了 pthread_mutex_lock() ?pthread_mutex_unlock() 函数调用。在U程E序中这些调用执行了不可或缺的功能。他们提供了一U?怺排斥的方法(互斥对象即由此得名)。两个线E不能同时对同一个互斥对象加锁?

互斥对象是这样工作的。如果线E?a 试图锁定一个互斥对象,而此时线E?b 已锁定了同一个互斥对象时Q线E?a 将q入睡眠状态。一旦线E?b 释放了互斥对象(通过 pthread_mutex_unlock() 调用Q,U程 a p够锁定这个互斥对象(换句话说Q线E?a 将?pthread_mutex_lock() 函数调用中返回,同时互斥对象被锁定)。同样地Q当U程 a 正锁定互斥对象时Q如果线E?c 试图锁定互斥对象的话Q线E?c 也将临时q入睡眠状态。对已锁定的互斥对象上调?pthread_mutex_lock() 的所有线E都进入睡眠状态,q些睡眠的线E将“排队”讉Kq个互斥对象?/p>

通常使用 pthread_mutex_lock() ?pthread_mutex_unlock() 来保护数据结构。这是_通过U程的锁定和解锁Q对于某一数据l构Q确保某一时刻只能有一个线E能够访问它。可以推到Q当U程试图锁定一个未加锁的互斥对象时QPOSIX U程库将同意锁定Q而不会ɾU程q入睡眠状态?/p>
Lq幅L的O画,四个精灵重C最q一?pthread_mutex_lock() 调用的一个场面?/strong>

图中Q锁定了互斥对象的线E能够存取复杂的数据l构Q而不必担心同时会有其它线E干扰。那个数据结构实际上?#8220;ȝ”了,直到互斥对象被解锁ؓ止。pthread_mutex_lock() ?pthread_mutex_unlock() 函数调用Q如?#8220;在施工中”标志一P正在修改和d的某一特定׃n数据包围h。这两个函数调用的作用就是警告其它线E,要它们l睡眠ƈ{待轮到它们对互斥对象加锁。当Ӟ除非?每个 对特定数据结构进行读写操作的语句前后Q都分别放上 pthread_mutex_lock() ?pthread_mutext_unlock() 调用Q才会出现这U情c?br>

Z么要用互斥对象?

听上d有趣Q但I竟Z么要让线E睡眠呢Q要知道Q线E的主要优点不就是其h独立工作、更多的时候是同时工作的能力吗Q是的,实是这栗然而,每个重要的线E程序都需要用某些互斥对象。让我们再看一下示例程序以便理解原因所在?/p>

L thread_function()Q@环中一开始就锁定了互斥对象,最后才它解锁。在q个CZE序中,mymutex 用来保护 myglobal 的倹{仔l查?thread_function()Q加一代码?myglobal 复制C个局部变量,对局部变量加一Q睡眠一U钟Q在q之后才把局部变量的g回给 myglobal。不使用互斥对象Ӟ即ȝE在 thread_function() U程睡眠一U钟期间内对 myglobal 加一Qthread_function() 苏醒后也会覆盖主U程所加的倹{用互斥对象能够保证这U情形不会发生。(您也怼惛_Q我增加了一U钟延迟以触发不正确的结果。把局部变量的Dl?myglobal 之前Q实际上没有什么真正理p?thread_function() 睡眠一U钟。)使用互斥对象的新E序产生了期望的l果Q?/p>

$ ./thread3
            o..o..o.o..o..o.o.o.o.o..o..o..o.ooooooo
            myglobal equals 40
            

Zq一步探索这个极为重要的概念Q让我们看一看程序中q行加一操作的代码:

thread_function() 加一代码Q?
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);
            myglobal=j;
            ȝE加一代码Q?
            myglobal=myglobal+1;
            

如果代码是位于单U程E序中,可以预期 thread_function() 代码完整执行。接下来才会执行ȝE代码(或者是以相反的序执行Q。在不用互斥对象的U程E序中,代码可能Q几乎是Q由于调用了 sleep() 的缘故)以如下的序执行Q?/p>

    thread_function() U程        ȝE?
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);                     myglobal=myglobal+1;
            myglobal=j;
            

当代码以此特定顺序执行时Q将覆盖ȝE对 myglobal 的修攏V程序结束后Q就得C正确的倹{如果是在操U|针的话,可能生段错误。注意到 thread_function() U程按顺序执行了它的所有指令。看来不象是 thread_function() 有什么次序颠倒。问题是Q同一旉内,另一个线E对同一数据l构q行了另一个修攏V?/p>


U程内幕 1

在解释如何确定在何处使用互斥对象之前Q先来深入了解一下线E的内部工作机制。请看第一个例子:

假设ȝE将创徏三个新线E:U程 a、线E?b 和线E?c。假定首先创建线E?aQ然后是U程 bQ最后创建线E?c?/p>

    pthread_create( &thread_a, NULL, thread_function, NULL);
            pthread_create( &thread_b, NULL, thread_function, NULL);
            pthread_create( &thread_c, NULL, thread_function, NULL);
            

在第一?pthread_create() 调用完成后,可以假定U程 a 不是已存在就是已l束q停止。第二个 pthread_create() 调用后,ȝE和U程 b 都可以假定线E?a 存在Q或已停止)?/p>

然而,在W二?create() 调用q回后,ȝE无法假定是哪一个线E(a ?bQ会首先开始运行。虽然两个线E都已存在,U程 CPU 旉片的分配取决于内核和U程库。至于谁首先运行,q没有严格的规则。尽线E?a 更有可能在线E?b 之前开始执行,但这q无保证。对于多处理器系l,情况更是如此。如果编写的代码假定在线E?b 开始执行之前实际上执行U程 a 的代码,那么Q程序最l正运行的概率?99%。或者更p糕Q程序在您的机器?100% 地正运行,而在您客L四处理器服务器上正确q行的概率却是零?/p>

从这个例子还可以得知Q线E库保留了每个单独线E的代码执行序。换句话_实际上那三个 pthread_create() 调用按它们出现的顺序执行。从ȝE上来看Q所有代码都是依ơ执行的。有Ӟ可以利用q一Ҏ优化部分U程E序。例如,在上例中Q线E?c 可以假定线E?a 和线E?b 不是正在q行是已经l止。它不必担心存在q没有创建线E?a 和线E?b 的可能性。可以用这一逻辑来优化线E程序?/p>


U程内幕 2

现在来看另一个假想的例子。假设有许多U程Q他们都正在执行下列代码Q?/p>

    myglobal=myglobal+1;
            

那么Q是否需要在加一操作语句前后分别锁定和解锁互斥对象呢Q也许有Z?#8220;?#8221;。编译器极有可能把上q赋D句编译成一条机器指令。大安知道Q不可能"半?中断一条机器指令。即使是g中断也不会破坏机器指令的完整性。基于以上考虑Q很可能們֐于完全省?pthread_mutex_lock() ?pthread_mutex_unlock() 调用。不要这样做?/p>

我在说废话吗Q不完全是这栗首先,不应该假定上q赋D句一定会被编译成一条机器指令,除非亲自验证了机器代码。即使插入某些内嵌汇~语句以保加一操作的完整执行――甚臻I即是自己动手写~译器!-- 仍然可能有问题?/p>

{案在这里。用单条内嵌汇~操作码在单处理器系l上可能不会有什么问题。每个加一操作都将完整地进行,q且多半会得到期望的l果。但是多处理器系l则截然不同。在?CPU 机器上,两个单独的处理器可能会在几乎同一时刻Q或者,在同一时刻Q执行上q赋D句。不要忘了,q时对内存的修改需要先?L1 写入 L2 高速缓存、然后才写入d。(SMP 机器q不只是增加了处理器而已Q它q有用来仲裁?RAM 存取的特D硬件。)最l,Ҏ无法搞清在写入主存的竞争中,哪个 CPU 会"胜出"。要产生可预的代码Q应使用互斥对象。互斥对象将插入一?内存兛_"Q由它来保对主存的写入按照U程锁定互斥对象的顺序进行?/p>

考虑一U以 32 位块为单位更C存的 SMP 体系l构。如果未使用互斥对象对一?64 位整数进行加一操作Q整数的最?4 位字节可能来自一?CPUQ而其?4 个字节却来自另一 CPU。糟p吧Q最p糕的是Q用差劲的技术,您的E序在重要客Lpȝ上有可能不是很长旉才崩溃一ơ,是早上三点钟就崩溃。David R. Butenhof 在他的《POSIX U程~程》(请参阅本文末 参考资?/font>部分Q一书中Q讨Z׃未用互斥对象而将产生的种U情c?


许多互斥对象

如果攄了过多的互斥对象Q代码就没有什么ƈ发性可aQ运行v来也比单U程解决Ҏ慢。如果放|了q少的互斥对象,代码出现奇怪和令h尬的错误。幸q的是,有一个中间立场。首先,互斥对象是用于串行化存取*׃n数据*。不要对非共享数据用互斥对象,q且Q如果程序逻辑保M时候都只有一个线E能存取特定数据l构Q那么也不要使用互斥对象?/p>

其次Q如果要使用׃n数据Q那么在诅R写׃n数据旉应用互斥对象。用 pthread_mutex_lock() ?pthread_mutex_unlock() 把读写部分保护v来,或者在E序中不固定的地斚wZ用它们。学会从一个线E的角度来审视代码,q确保程序中每一个线E对内存的观炚w是一致和合适的。ؓ了熟悉互斥对象的用法Q最初可能要花好几个时来编写代码,但是很快׃习惯q且*?不必多想p够正用它们?/p>


使用调用Q初始化

现在该来看看使用互斥对象的各U不同方法了。让我们从初始化开始。在 thread3.c CZ 中,我们使用了静态初始化Ҏ。这需要声明一?pthread_mutex_t 变量Qƈ赋给它常?PTHREAD_MUTEX_INITIALIZERQ?

pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
            

很简单吧。但是还可以动态地创徏互斥对象。当代码使用 malloc() 分配一个新的互斥对象时Q用这U动态方法。此Ӟ静态初始化Ҏ是行不通的Qƈ且应当用例E?pthread_mutex_init()Q?/p>

int pthread_mutex_init( pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr)
            

正如所C,pthread_mutex_init 接受一个指针作为参C初始化ؓ互斥对象Q该指针指向一块已分配好的内存区。第二个参数Q可以接受一个可选的 pthread_mutexattr_t 指针。这个结构可用来讄各种互斥对象属性。但是通常q不需要这些属性,所以正常做法是指定 NULL?/p>

一旦?pthread_mutex_init() 初始化了互斥对象Q就应?pthread_mutex_destroy() 消除它。pthread_mutex_destroy() 接受一个指?pthread_mutext_t 的指针作为参敎ͼq攑ֈZ斥对象时分配l它的Q何资源。请注意Q?pthread_mutex_destroy() 不会 释放用来存储 pthread_mutex_t 的内存。释放自q内存完全取决于您。还必须注意一点,pthread_mutex_init() ?pthread_mutex_destroy() 成功旉q回零?


使用调用Q锁?/font>

pthread_mutex_lock(pthread_mutex_t *mutex)
            

pthread_mutex_lock() 接受一个指向互斥对象的指针作ؓ参数以将光定。如果碰巧已l锁定了互斥对象Q调用者将q入睡眠状态。函数返回时Q将唤醒调用者(昄Qƈ且调用者还保留该锁。函数调用成功时q回Ӟp|时返回非零的错误代码?/p>

pthread_mutex_unlock(pthread_mutex_t *mutex)
            

pthread_mutex_unlock() ?pthread_mutex_lock() 盔R合,它把U程已经加锁的互斥对象解锁。始l应该尽快对已加锁的互斥对象q行解锁Q以提高性能Q。ƈ且绝对不要对您未保持锁的互斥对象q行解锁操作Q否则,pthread_mutex_unlock() 调用失败ƈ带一个非零的 EPERM q回|?/p>

pthread_mutex_trylock(pthread_mutex_t *mutex)
            

当线E正在做其它事情的时候(׃互斥对象当前是锁定的Q,如果希望锁定互斥对象Q这个调用就相当方便。调?pthread_mutex_trylock() 时将试锁定互斥对象。如果互斥对象当前处于解锁状态,那么您将获得该锁q且函数返回零。然而,如果互斥对象已锁定,q个调用也不会阻塞。当Ӟ它会q回非零?EBUSY 错误倹{然后可以l做其它事情Q稍后再试锁定?/p>


{待条g发生

互斥对象是线E程序必需的工P但它们ƈ非万能的。例如,如果U程正在{待׃n数据内某个条件出玎ͼ那会发生什么呢Q代码可以反复对互斥对象锁定和解锁,以检查值的M变化。同Ӟq要快速将互斥对象解锁Q以便其它线E能够进行Q何必需的更攏V这是一U非常可怕的ҎQ因为线E需要在合理的时间范围内频繁地@环检变化?/p>

在每ơ检查之_可以让调用线E短暂地q入睡眠Q比如睡眠三U钟Q但是因此线E代码就无法最快作出响应。真正需要的是这样一U方法,当线E在{待满某些条g时ɾU程q入睡眠状态。一旦条件满Iq需要一U方法以唤醒因等待满特定条件而睡眠的U程。如果能够做到这一点,U程代码是非常高效的,q且不会占用宝贵的互斥对象锁。这正是 POSIX 条g变量能做的事Q?/p>

?POSIX 条g变量是我下一文章的主题Q其中将说明如何正确使用条g变量。到那时Q您拥有了创徏复杂U程E序所需的全部资源,那些U程E序可以模拟工作人员、装配线{等。既然您已经来熟悉线E,我将在下一文章中加快q度。这P在下一文章的l尾p放上一个相对复杂的U程E序。说到等到条件生,下次再见Q?/p>



参考资?



关于作?/font>

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQ?Gentoo 目的总设计师Q多?MacMillan 出版书籍的作者,包括Q?Caldera OpenLinux Unleashed?SuSE Linux Unleashed?Samba Unleashed 。Daniel 自小学二q起就与计机l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和刚出生的女?Hadassah 一起共渡时光。可通过 drobbins@gentoo.org ?Daniel 取得联系?

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQ?Gentoo 目的总设计师Q多?MacMillan 出版书籍的作者,包括Q?Caldera OpenLinux Unleashed?SuSE Linux Unleashed?Samba Unleashed 。Daniel 自小学二q起就与计机l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和刚出生的女?Hadassah 一起共渡时光。可通过 drobbins@gentoo.org ?Daniel 取得联系?









沙漠里的 2010-03-11 11:46 发表评论
]]>[转蝲]POSIX U程详解-W?部分http://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109415.html沙漠里的沙漠里的Thu, 11 Mar 2010 03:39:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109415.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/109415.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/11/109415.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/109415.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/109415.html

[原文地址]http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/


POSIXQ可UL操作pȝ接口Q线E是提高代码响应和性能的有力手Dc在本系列中QDaniel Robbins 向您_地展C在~程中如何用线E。其中还涉及大量q后l节Q读完本pd文章Q您完全可以q用 POSIX U程创徏多线E程序?/p>

U程是有的

了解如何正确q用U程是每一个优UE序员必备的素质。线E类gq程。如同进E,U程由内核按旉分片q行理。在单处理器pȝ中,内核使用旉分片来模拟线E的q发执行Q这U方式和q程的相同。而在多处理器pȝ中,如同多个q程Q线E实际上一样可以ƈ发执行?/p>

那么Z么对于大多数合作性Q务,多线E比多个独立的进E更优越呢?q是因ؓQ线E共享相同的内存I间。不同的U程可以存取内存中的同一个变量。所以,E序中的所有线E都可以L写声明过的全局变量。如果曾?fork() ~写q重要代码,׃认识到这个工L重要性。ؓ什么呢Q虽?fork() 允许创徏多个q程Q但它还会带来以下通信问题: 如何让多个进E相互通信Q这里每个进E都有各自独立的内存I间。对q个问题没有一个简单的{案。虽然有许多不同U类的本?IPC (q程间通信Q,但它们都遇到两个重要障碍Q?/p>

  • 强加了某UŞ式的额外内核开销Q从而降低性能?
  • 对于大多数情形,IPC 不是对于代码?#8220;自然”扩展。通常极大地增加了E序的复杂性?

双重坏事: 开销和复杂性都非好事。如果曾lؓ了支?IPC 而对E序大动q戈q,那么您就会真正欣赏线E提供的单共享内存机制。由于所有的U程都驻留在同一内存I间QPOSIX U程无需q行开销大而复杂的长距调用。只要利用简单的同步机制Q程序中所有的U程都可以读取和修改已有的数据结构。而无需数据经由文件描q符转储或挤入紧H的׃n内存I间。仅此一个原因,p以让您考虑应该采用单进E?多线E模式而非多进E?单线E模式?/p>

U程是快L

不仅如此。线E同栯是非常快L。与标准 fork() 相比Q线E带来的开销很小。内核无需单独复制q程的内存空间或文g描述W等{。这p省了大量?CPU 旉Q得线E创建比新进E创建快上十C癑ր。因一点,可以大量使用U程而无需太过于担心带来的 CPU 或内存不뀂?fork() 时导致的大量 CPU 占用也不复存在。这表示只要在程序中有意义,通常可以创建线E?/p>

当然Q和q程一PU程利用多 CPU。如果Y件是针对多处理器pȝ设计的,q就真的是一大特性(如果软g是开放源码,则最l可能在不少q_上运行)。特定类型线E程序(其?CPU 密集型程序)的性能随pȝ中处理器的数目几乎线性地提高。如果正在编?CPU 非常密集型的E序Q则l对惌法在代码中用多U程。一旦掌握了U程~码Q无需使用J琐?IPC 和其它复杂的通信机制Q就能够以全新和创造性的Ҏ解决~码N。所有这些特性配合在一起得多U程~程更有、快速和灉|?/p>

U程是可UL?/span>

如果熟悉 Linux ~程Q就有可能知?__clone() pȝ调用。__clone() cM?fork()Q同时也有许多线E的Ҏ。例如,使用 __clone()Q新的子q程可以有选择地共享父q程的执行环境(内存I间Q文件描q符{)。这是好的一面。但 __clone() 也有不之处。正如__clone() 在线帮助指出Q?/p>

“__clone 调用是特定于 Linux q_的,不适用于实现可UL的程序。欲~写U程化应用程序(多线E控制同一内存I间Q,最好用实?POSIX 1003.1c U程 API 的库Q例?Linux-Threads 库。参?pthread_create(3thr)?#8221;

虽然 __clone() 有线E的许多Ҏ,但它是不可移植的。当然这q不意味着代码中不能用它。但在Y件中考虑使用 __clone() 时应当权衡这一事实。值得庆幸的是Q正?__clone() 在线帮助指出Q有一U更好的替代ҎQPOSIX U程。如果想~写 可移植的 多线E代码,代码可运行于 Solaris、FreeBSD、Linux 和其它^収ͼPOSIX U程是一U当然之选?/p>

W一个线E?/span>

下面是一?POSIX U程的简单示例程序:


thread1.c

#include <pthread.h>
            #include <stdlib.h>
            #include <unistd.h>
            void *thread_function(void *arg) {
            int i;
            for ( i=0; i<20; 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);
            }
            

要编译这个程序,只需先将E序存ؓ thread1.cQ然后输入:

$ gcc thread1.c -o thread1 -lpthread
            

q行则输入:

$ ./thread1
            


理解 thread1.c

thread1.c 是一个非常简单的U程E序。虽然它没有实现什么有用的功能Q但可以帮助理解U程的运行机制。下面,我们一步一步地了解q个E序是干什么的。main() 中声明了变量 mythreadQ类型是 pthread_t。pthread_t cd?pthread.h 中定义,通常UCؓ“U程 id”Q羃写ؓ "tid"Q。可以认为它是一U线E句柄?/p>

mythread 声明后(C mythread 只是一?"tid"Q或是将要创建的U程的句柄)Q调?pthread_create 函数创徏一个真实活动的U程。不要因?pthread_create() ?"if" 语句内而受其迷惑。由?pthread_create() 执行成功时返回零而失败时则返回非零|?pthread_create() 函数调用攑֜ if() 语句中只是ؓ了方便地失败的调用。让我们查看一?pthread_create 参数。第一个参?&mythread 是指?mythread 的指针。第二个参数当前?NULLQ可用来定义U程的某些属性。由于缺省的U程属性是适用的,只需该参数设ؓ NULL?/p>

W三个参数是新线E启动时调用的函数名。本例中Q函数名?thread_function()。当 thread_function() q回Ӟ新线E将l止。本例中Q线E函数没有实现大的功能。它仅将 "Thread says hi!" 输出 20 ơ然后退出。注?thread_function() 接受 void * 作ؓ参数Q同时返回值的cd也是 void *。这表明可以?void * 向新U程传递Q意类型的数据Q新U程完成时也可返回Q意类型的数据。那如何向线E传递一个Q意参敎ͼ很简单。只要利?pthread_create() 中的W四个参数。本例中Q因为没有必要将M数据传给微不道?thread_function()Q所以将W四个参数设?NULL?/p>

您也许已推测刎ͼ?pthread_create() 成功q回之后Q程序将包含两个U程。等一{, 两个 U程Q我们不是只创徏了一个线E吗Q不错,我们只创Z一个进E。但是主E序同样也是一个线E。可以这L解:如果~写的程序根本没有?POSIX U程Q则该程序是单线E的Q这个单U程UCؓ“?#8221;U程Q。创Z个新U程之后E序d有两个U程了?

我想此时您至有两个重要问题。第一个问题,新线E创Z后主U程如何q行。答案,ȝE按序l箋执行下一行程序(本例中执?"if (pthread_join(...))"Q。第二个问题Q新U程l束时如何处理。答案,新线E先停止Q然后作为其清理q程的一部分Q等待与另一个线E合q或“q接”?/p>

现在Q来看一?pthread_join()。正?pthread_create() 一个线E拆分ؓ两个Q?pthread_join() 两个线E合qؓ一个线E。pthread_join() 的第一个参数是 tid mythread。第二个参数是指?void 指针的指针。如?void 指针不ؓ NULLQpthread_join 线E的 void * q回值放|在指定的位|上。由于我们不必理?thread_function() 的返回|所以将其设?NULL.

您会注意?thread_function() ׃ 20 U才完成。在 thread_function() l束很久之前Q主U程已l调用了 pthread_join()。如果发生这U情况,ȝE将中断Q{向睡眠)然后{待 thread_function() 完成。当 thread_function() 完成? pthread_join() 返回。这时程序又只有一个主U程。当E序退出时Q所有新U程已经使用 pthread_join() 合ƈ了。这是应该如何处理在程序中创徏的每个新U程的过E。如果没有合q一个新U程Q则它仍然对pȝ的最大线E数限制不利。这意味着如果未对U程做正的清理Q最l会D pthread_create() 调用p|?/p>

无父Q无?/span>

如果使用q?fork() pȝ调用Q可能熟悉父q程和子q程的概c当?fork() 创徏另一个新q程Ӟ新进E是子进E,原始q程是父q程。这创徏了可能非常有用的层次关系Q尤其是{待子进E终止时。例如,waitpid() 函数让当前进E等待所有子q程l止。waitpid() 用来在父q程中实现简单的清理q程?/p>

?POSIX U程更有意思。您可能已经注意到我一直有意避免?#8220;父线E?#8221;?#8220;子线E?#8221;的说法。这是因?POSIX U程中不存在q种层次关系。虽然主U程可以创徏一个新U程Q新U程可以创徏另一个新U程QPOSIX U程标准它们视为等同的层次。所以等待子U程退出的概念在这里没有意义。POSIX U程标准不记录Q?#8220;家族”信息。缺家族信息有一个主要含意:如果要等待一个线E终止,必dU程?tid 传递给 pthread_join()。线E库无法为您断定 tid?/p>

对大多数开发者来说这不是个好消息Q因会有多个线E的E序复杂化。不q不要ؓ此担忧。POSIX U程标准提供了有效地理多个U程所需要的所有工兗实际上Q没有父/子关p这一事实却ؓ在程序中使用U程开辟了更创造性的Ҏ。例如,如果有一个线E称为线E?1Q线E?1 创徏了称为线E?2 的线E,则线E?1 自己没有必要调用 pthread_join() 来合q线E?2Q程序中其它MU程都可以做到。当~写大量使用U程的代码时Q这可能允许发生有的事情。例如,可以创徏一个包含所有已停止U程的全局“ȝE列?#8221;Q然后让一个专门的清理U程专等停止的线E加到列表中。这个清理线E调?pthread_join() 刚停止的线E与自己合ƈ。现在,仅用一个线E就巧妙和有效地处理了全部清理?br>

同步漫游

现在我们来看一些代码,q些代码做了一些意想不到的事情。thread2.c 的代码如下:


thread2.c
#include <pthread.h>
            #include <stdlib.h>
            #include <unistd.h>
            #include <stdio.h>
            int myglobal;
            void *thread_function(void *arg) {
            int i,j;
            for ( i=0; i<20; i++) {
            j=myglobal;
            j=j+1;
            printf(".");
            fflush(stdout);
            sleep(1);
            myglobal=j;
            }
            return NULL;
            }
            int main(void) {
            pthread_t mythread;
            int i;
            if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
            printf("error creating thread.");
            abort();
            }
            for ( i=0; i<20; i++) {
            myglobal=myglobal+1;
            printf("o");
            fflush(stdout);
            sleep(1);
            }
            if ( pthread_join ( mythread, NULL ) ) {
            printf("error joining thread.");
            abort();
            }
            printf("\nmyglobal equals %d\n",myglobal);
            exit(0);
            }
            

理解 thread2.c

如同W一个程序,q个E序创徏一个新U程。主U程和新U程都将全局变量 myglobal 加一 20 ơ。但是程序本w生了某些意想不到的结果。编译代码请输入Q?/p>

$ gcc thread2.c -o thread2 -lpthread
            

q行误入:

$ ./thread2
            

输出Q?/p>

$ ./thread2
            ..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
            myglobal equals 21
            

非常意外吧!因ؓ myglobal 从零开始,ȝE和新线E各自对其进行了 20 ơ加一, E序l束?myglobal 值应当等?40。由?myglobal 输出l果?21Q这其中肯定有问题。但是究竟是什么呢Q?/p>

攑ּ吗?好,让我来解释是怎么一回事。首先查看函?thread_function()。注意如何将 myglobal 复制到局部变?"j" 了吗? 接着?j 加一, 再睡眠一U,然后到这时才新?j 值复制到 myglobalQ这是关键所在。设想一下,如果ȝE就在新U程?myglobal 值复制给 j ?/strong> 立即?myglobal 加一Q会发生什么??thread_function() ?j 的值写?myglobal Ӟp盖了ȝE所做的修改?

当编写线E程序时Q应避免产生q种无用的副作用Q否则只会浪Ҏ_当然Q除了编写关?POSIX U程的文章时有用Q。那么,如何才能排除q种问题呢?

׃是将 myglobal 复制l?j q且{了一U之后才写回时生问题,可以试避免使用临时局部变量ƈ直接?myglobal 加一。虽然这U解x案对q个特定例子适用Q但它还是不正确。如果我们对 myglobal q行相对复杂的数学运,而不是简单的加一Q这U方法就会失效。但是ؓ什么呢Q?/p>

要理解这个问题,必须CU程是ƈ发运行的。即使在单处理器pȝ上运行(内核利用旉分片模拟多Q务)也是可以的,从程序员的角度,惛_两个U程是同时执行的。thread2.c 出现问题是因?thread_function() 依赖以下论据Q在 myglobal 加一之前的大U一U钟期间不会修改 myglobal。需要有些途径让一个线E在?myglobal 做更Ҏ通知其它U程“不要靠近”。我在下一文章中讲解如何做到q一炏V到时候见?/p>


参考资?



关于作?/span>

 

Daniel Robbins 居住在新墨西哥州?Albuquerque。他?Gentoo Technologies, Inc. 的总裁?CEOQ?Gentoo 目的总设计师Q多?MacMillan 出版书籍的作者,包括Q?Caldera OpenLinux Unleashed?SuSE Linux Unleashed?Samba Unleashed 。Daniel 自小学二q起就与计机l下不解之缘Q那时他首先接触的是 Logo E序语言Qƈ沉h?Pac-Man 游戏中。这也许是他至今仍担Q SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻?Mary 和刚出生的女?Hadassah 一起共渡时光。可通过 drobbins@gentoo.org ?Daniel Robbins 取得联系?



沙漠里的 2010-03-11 11:39 发表评论
]]>
Linux下用信号量实现对׃n内存的访问保??http://m.shnenglu.com/zjl-1026-2001/archive/2010/03/03/108778.html沙漠里的沙漠里的Wed, 03 Mar 2010 05:49:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/03/108778.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/108778.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/03/108778.html#Feedback1http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/108778.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/108778.html1Q很多资料中讲到Q系lV׃n内存是随内核持箋的,即所有访问共享内存的q程都已l正常终止,׃n内存仍然存在Q在内核引导之前Q对该共享内存区域的M改写操作都将一直保留,除非昑ּ删除׃n内存。的是q样的,pȝV机制分配的共享内存将一直保留,若要删除要么重启pȝQ要么就调用shmctl来显C删除。那么,shmctl怎么使用呢?q在下面会讲到?br>
2Q对于两个不同的q程Q如何在其中一个进E满xU条件时唤醒另一个进E呢Q下面也会讲到?br>
.....................................(待箋)

沙漠里的 2010-03-03 13:49 发表评论
]]>
Linux下用信号量实现对׃n内存的访问保?一)http://m.shnenglu.com/zjl-1026-2001/archive/2010/03/03/108768.html沙漠里的沙漠里的Wed, 03 Mar 2010 03:42:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/03/108768.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/108768.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2010/03/03/108768.html#Feedback2http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/108768.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/108768.html阅读全文

沙漠里的 2010-03-03 11:42 发表评论
]]>
Linux下socket~程中的若干问题(持箋更新?http://m.shnenglu.com/zjl-1026-2001/archive/2009/08/20/93914.html沙漠里的沙漠里的Thu, 20 Aug 2009 07:52:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2009/08/20/93914.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/93914.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2009/08/20/93914.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/93914.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/93914.html(1) 最q在linux下开发了一个通信服务E序Q主要负责与客户端徏立连接,转发客户端的消息l后C息处理模块,同时也将后台的处理结果{发给客户端?br>׃在windows下已l有了一个相同功能的E序Q便做了UL。移植到linux下功能是可以实现的,但发现此E序的cpu利用率非帔R。经分析发现是linux下的
select调用与windows的select调用的一个区别造成的?br>
E序处理程如下Q?br>
 1bool msg_recv_thread(void)
 2{
 3    int          max = 0;
 4    fd_set       readfds;         // l果?/span>
 5    struct timeval  RevTimeOut;
 6    RevTimeOut.tv_sec = 1;        // 讑֮select的超时时间ؓ1s
 7    RevtimeOut.tv_usec = 0;
 8
 9    while(1)
10    {
11        FD_SET(conn_socket, &readfds);
12        max = (max > conn_socket) ? max : conn_socket;
13        int ret = select(max+1&readfds, NULL, NULL, &RevTimeOut);
14
15        if (ret <= 0)
16        {
17            continue;
18        }

19
20        if (FD_ISSET(conn_socket, &readfds) != 0)
21        {
22            // 接受q接h处理……
23        }

24
25        // 其他处理……
26}

windows下这L程没有问题Q但是在linux下,select调用在设定的时旉内等待时会不断地更新最后一个参敎ͼ其实时更新为离讑֮的超时时间的旉差,直到q个D更新?Q即到达时旉时select函数q回。在上面的程序段中,W一ơ@环时select的超时参数gؓ1sQ当W一ơ@环完毕时QRevTimeOut的值已l被变成?Q这样以后的循环׃是无d的,卛_果selec没有收到M的请求便立刻q回Q然后l@环,q样Ş成了d@环,从而耗光了cpu?br>
上q程序段中的5-7行移到第13行以前,问题便解决了?br>

【ȝ?br>            q里涉及C个编E习惯的问题Q本人经验欠~,在做windows到linux的移植时一直认为既然windows下正那么linux一定也是正的Q完全没有考虑C个OS好之间的pȝ调用斚w的区别,D开始时搞错了方向Q浪费了不少旉。希望大家不要犯我这L错误?br>

沙漠里的 2009-08-20 15:52 发表评论
]]>
þŷƷ| þ99Ʒþþôѧ| 97þþƷ| ަvþþ| 7777þĻ| 91þùƵ| þþþþþþþþþƷ| avҹһƬѿþ| þǿdŮ| þһҹ | þøŮ߳MBA| ƷþþþþĻ| Ʒþ| ԭۺϾþô| þþ91Ʒһ | þ| þۺ| 2021ھƷþþþþӰԺ| һþ| þþƷþþþùۿ99ˮ| þþƷ18| ձþ| Ʒþ8xѹۿ| þþþþþƷѿSSS| þþþùƷ۲ӰԺ| 鶹Ʒþþһ| þAV˳׽| þǿdŮվ | ޾þþþþ77777| 66ƷۺϾþþþþþ| ƷŮٸaѾþ| ģ˽ĹƷþ| þþþ97Һ| ݺݾþŷר | ɫþˬˬƬaV | þþþþþƷ| þۺϾƷһ | þþƷ72| þþþӰԺС| 99þѹƷ| 91þþƷֱ|