posts - 200, comments - 8, trackbacks - 0, articles - 0

五种I/O 模式:
【1】        阻塞 I/O           (Linux下的I/O操作默认是阻塞I/O,即open和socket创建的I/O都是阻塞I/O)
【2】        非阻塞 I/O        (可以通过fcntl或者open时使用O_NONBLOCK参数,将fd设置为非阻塞的I/O)
【3】        I/O 多路复用     (I/O多路复用,通常需要非阻塞I/O配合使用)
【4】        信号驱动 I/O    (SIGIO)
【5】        异步 I/O

 

一般来说,程序进行输入操作有两步:
1.等待有数据可以读
2.将数据从系统内核中拷贝到程序的数据区。

对于sock编程来说:

         第一步:   一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;

         第二步:   是从内核中把数据拷贝到程序的数据区中。

 

阻塞I/O模式                            //进程处于阻塞模式时,让出CPU,进入休眠状态
        阻塞 I/O 模式是最普遍使用的 I/O 模式。是Linux系统下缺省的IO模式。

       大部分程序使用的都是阻塞模式的 I/O 。

       一个套接字建立后所处于的模式就是阻塞 I/O 模式。(因为Linux系统默认的IO模式是阻塞模式)


对于一个 UDP 套接字来说,数据就绪的标志比较简单:
(1)已经收到了一整个数据报
(2)没有收到。
而 TCP 这个概念就比较复杂,需要附加一些其他的变量。

       一个进程调用 recvfrom  ,然后系统调用并不返回知道有数据报到达本地系统,然后系统将数据拷贝到进程的缓存中。 (如果系统调用收到一个中断信号,则它的调用会被中断)

   我们称这个进程在调用recvfrom一直到从recvfrom返回这段时间是阻塞的。当recvfrom正常返回时,我们的进程继续它的操作。

 
 ---------------------------------------------------------------------------------

非阻塞模式I/O                           //非阻塞模式的使用并不普遍,因为非阻塞模式会浪费大量的CPU资源。
       当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核: “当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
      我们开始对 recvfrom 的三次调用,因为系统还没有接收到网络数据,所以内核马上返回一个 EWOULDBLOCK的错误。

      第四次我们调用 recvfrom 函数,一个数据报已经到达了,内核将它拷贝到我们的应用程序的缓冲区中,然后 recvfrom 正常返回,我们就可以对接收到的数据进行处理了。
      当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不听的测试是否一个文件描述符有数据可读(称做 polling(轮询))。应用程序不停的 polling 内核来检查是否 I/O操作已经就绪。这将是一个极浪费 CPU资源的操作。这种模式使用中不是很普遍。

 
例如:
         对管道的操作,最好使用非阻塞方式!
 
 
  ---------------------------------------------------------------------------------
 

I/O多路复用                             //针对批量IP操作时,使用I/O多路复用,非常有好。

       在使用 I/O 多路技术的时候,我们调用 select()函数和 poll()函数或epoll函数(2.6内核开始支持),在调用它们的时候阻塞,而不是我们来调用 recvfrom(或recv)的时候阻塞。
       当我们调用 select函数阻塞的时候,select 函数等待数据报套接字进入读就绪状态。当select函数返回的时候, 也就是套接字可以读取数据的时候。 这时候我们就可以调用 recvfrom函数来将数据拷贝到我们的程序缓冲区中。
        对于单个I/O操作,和阻塞模式相比较,select()和poll()或epoll并没有什么高级的地方。
        而且,在阻塞模式下只需要调用一个函数:
                             读取或发送函数。
                   在使用了多路复用技术后,我们需要调用两个函数了:
                             先调用 select()函数或poll()函数,然后才能进行真正的读写。

       多路复用的高级之处在于::

              它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

 
IO 多路技术一般在下面这些情况中被使用:
1、当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输入输出和网络套接字),I/O 多路复用技术将会有机会得到使用。
2、当程序需要同时进行多个套接字的操作的时候。
3、如果一个 TCP 服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。
4、如果一个服务器程序同时使用 TCP 和 UDP 协议。
5、如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如 inetd就是这样的)。
 
 
 
异步IO模式有::
       1、信号驱动I/O模式
       2、异步I/O模式
信号驱动I/O模式                                                   //自己没有用过。

       我们可以使用信号,让内核在文件描述符就绪的时候使用 SIGIO 信号来通知我们。我们将这种模式称为信号驱动 I/O 模式。


为了在一个套接字上使用信号驱动 I/O 操作,下面这三步是所必须的。

(1)一个和 SIGIO信号的处理函数必须设定。
(2)套接字的拥有者必须被设定。一般来说是使用 fcntl 函数的 F_SETOWN 参数来
进行设定拥有者。
(3)套接字必须被允许使用异步 I/O。一般是通过调用 fcntl 函数的 F_SETFL 命令,O_ASYNC为参数来实现。

       虽然设定套接字为异步 I/O 非常简单,但是使用起来困难的部分是怎样在程序中断定产生 SIGIO信号发送给套接字属主的时候,程序处在什么状态。

1.UDP 套接字的 SIGIO 信号                    (比较简单)
在 UDP 协议上使用异步 I/O 非常简单.这个信号将会在这个时候产生:
1、套接字收到了一个数据报的数据包。
2、套接字发生了异步错误。
        当我们在使用 UDP 套接字异步 I/O 的时候,我们使用 recvfrom()函数来读取数据报数据或是异步 I/O 错误信息。
2.TCP 套接字的 SIGIO 信号                   (不会使用)
          不幸的是,异步 I/O 几乎对 TCP 套接字而言没有什么作用。因为对于一个 TCP 套接字来说,SIGIO 信号发生的几率太高了,所以 SIGIO 信号并不能告诉我们究竟发生了什么事情。
在 TCP 连接中, SIGIO 信号将会在这个时候产生:
l  在一个监听某个端口的套接字上成功的建立了一个新连接。
l  一个断线的请求被成功的初始化。
l  一个断线的请求成功的结束。
l  套接字的某一个通道(发送通道或是接收通道)被关闭。
l  套接字接收到新数据。
l  套接字将数据发送出去。

l  发生了一个异步 I/O 的错误。

一个对信号驱动 I/O 比较实用的方面是 NTP(网络时间协议 Network Time Protocol)服务器,它使用 UDP。这个服务器的主循环用来接收从客户端发送过来的数据报数据包,然后再发送请求。对于这个服务器来说,记录下收到每一个数据包的具体时间是很重要的。

因为那将是返回给客户端的值,客户端要使用这个数据来计算数据报在网络上来回所花费的时间。图 6-8 表示了怎样建立这样的一个 UDP 服务器。

 

 

异步I/O模式             //比如写操作,只需用写,不一定写入磁盘(这就是异步I/O)的好处。异步IO的好处效率高。
      当我们运行在异步 I/O 模式下时,我们如果想进行 I/O 操作,只需要告诉内核我们要进行 I/O 操作,然后内核会马上返回。具体的 I/O 和数据的拷贝全部由内核来完成,我们的程序可以继续向下执行。当内核完成所有的 I/O 操作和数据拷贝后,内核将通知我们的程序。
异步 I/O 和  信号驱动I/O的区别是:
        1、信号驱动 I/O 模式下,内核在操作可以被操作的时候通知给我们的应用程序发送SIGIO 消息。

        2、异步 I/O 模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应用程序。

转自:http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520112163171778/

posted @ 2012-12-16 12:21 鑫龙 阅读(306) | 评论 (0)编辑 收藏

孤儿进程: 即一个其父进程已经终止的进程。 孤儿进程由 init 进程“收养”,init 进程ID为1,因此被收养的孤儿进程的父进程便更新为1。

孤儿进程组: 一个进程组中的所有进程的父进程要么是该进程组的一个进程,要么不是该进程组所在的会话中的进程。 一个进程组不是孤儿进程组的条件是,该组中有一个进程其父进程在属于同一个会话的另一个组中。

GNU解释了为什么会提出孤儿进程组的概念:
       When a controlling process terminates, its terminal becomes free and a new session can be established on it. (In fact, another user could log in on the terminal.) This could cause a problem if any processes from the old session are still trying to use that terminal.
To prevent problems, process groups that continue running even after the session leader has terminated are marked as orphaned process groups.
       When a process group becomes an orphan, its processes are sent a SIGHUP signal. Ordinarily, this causes the processes to terminate. However, if a program ignores this signal or establishes a handler for it, it can continue running as in the orphan process group even after its controlling process terminates; but it still cannot access the terminal any more.

当一个终端控制进程(即会话首进程)终止后,那么这个终端可以用来建立一个新的会话。这可能会产生一个问题,原来旧的会话(一个或者多个进程组的集合)中的任一进程可再次访问这个的终端。为了防止这类问题的产生,于是就有了孤儿进程组的概念。当一个进程组成为孤儿进程组时,posix.1要求向孤儿进程组中处于停止状态的进程发送SIGHUP(挂起)信号,系统对于这种信号的默认处理是终止进程,然而如果无视这个信号或者另行处理的话那么这个挂起进程仍可以继续执行。


以下摘自网络:

       终端的问题涉及几个概念,那就是进程组,会话,作业,下面会分别进行介绍。会话包含了一系列的进程,这些进程按照不同的执行内容会组织成若干进程组,一个会话内的所有进程都必须是该会话首长进程的后代,这样就保证了这些进程都是由该会话首长进程直接或者间接开启的,只有这样的才能保证这些进程确实是在会话首长进程耳目视线之内的,同时,孤儿进程组不再受到会话首长进程的控制。
作业:
只有一个终端,但是有很多事情要同时做,或者起码分时做,不能先做完一件事再做另一件,怎么办?毕竟启动一个进程后该进程就会独占终端啊,毕竟shell会将它设置为前台进程组进程啊。这就是作业的功能,只需要在一个命令后加一个&符号就可以了,比如我要执行x,那么就敲入:
x &
shell的结果是:
[1] 1234
此处1234是进程的pid,而1则是作业的id,这样这个x就不占用终端了,shell可以启动其它的进程或者作业了,比如又启动了作业2:
[2] 4321
       此时想起作业1需要使用一下终端来输入一些信息了,那么就使用:fg %1将作业1置于前台(作业1中目前只有一个进程),置于前台的作业如何重新放到后台呢?只需要用SIGSTOP信号停止前台使用终端的进程即可,然后该进程就放开终端的占用了
进程组:一个作业就是一个进程组,单独的进程可以独占一个进程组也可以加入同一会话的别的进程组,必须要满足的条件是,同一进程组的所有进程都要是一个会话的后代。所谓的进程组是为了组织作业或者组织同一类任务的。
控制终端:
       一个会话的建立者有权力申请一个控制终端,在该控制终端中可以接受标准输入,可以发送shell理解的控制快捷键,可以创建作业并且使用会话头进程提供的作业控制功能。控制终端只能由会话头进程创建,并且控制终端是独占的,只要有一个进程将一个终端当成了控制终端,别的进程不管它是谁都不能这么做了,tty_open中的最后有下面几行代码
if (!noctty &&
    current->signal->leader &&
    !current->signal->tty &&
    tty->session == 0) {
    task_lock(current);
    current->signal->tty = tty;
    task_unlock(current);
    current->signal->tty_old_pgrp = 0;
    tty->session = current->signal->session;  //设置session
    tty->pgrp = process_group(current);
}
可见别的进程是无权申请控制终端的。
     这个控制终端平时给谁用呢?最不容被怀疑的会话首长申请了终端,因为如果连他都值得怀疑的话,后面的属于他的孩子进程们都值得怀疑了,首长申请的终端就是给这些孩子们用的,首长将这些孩子们分成了若干的进程组,指定一个组为前台进程组,只有这个前台进程组的进程才能使用控制终端。bash一般会作为会话首长存在,bash将为一个执行的命令都创建一个进程组,它在接受一个命令准备执行的时候会将该进程设置为前台进程组,它在接受了命令行后加&的命令时,会创建一个作业,并且将它设定为后台进程组,那么此时前台是谁呢,是bash自己哦。后台进程不能使用终端,如果使用&执行了内部带有诸如getchar之类函数的进程,那么其将会收到SIGTTIN信号,不过可以使用fg命令将这类进程提到前台。
控制进程:
       很显然,首先控制进程是一个会话的首长进程,另外即使是会话首长也只能通过终端来控制别的进程,所谓的控制就是发送信号而不是操作内存之类的,这也是进程间通信的一种方式。因此所谓的控制进程就是申请到控制终端的进程。
孤儿进程组:
       有孤儿进程,对应的也有孤儿进程组的概念。为何引入这个概念以及这个概念的引入需要OS的实现者作些什么呢?先看两个前提,首先,posix用一个session的概念来描述一次用户的登录以及该用户在此次登录后的操作,然后用作业的概念描述不同操作的内容,最后才用进程的概念描述不同操作中某一个具体的工作;其次,unix最初将所有的进程组织成了树的形式,这样就便于追踪每个进程也便于管理(想想看,人类政治社会也是一个类似树形结构:君主专制,两院制等)。有了上述两个前提事情就很明白了,一切都是为了便于管理,一切都是为了登录用户的安全,即此次登录用户的作业是不能被下个登录用户所控制的,即使它们的用户名一致也是不行的,因此所谓的孤儿进程组简单点说就是脱离了创造它的session控制的,离开其session眼线的进程组,unix中怎样控制进程,怎样证明是否在自己的眼线内,那就是树形结构了,只要处于以自己为根的子树的进程就是自己眼线内的进程,这个进程就是受到保护的,有权操作的,而在别的树枝上的进程原则上是触动不得的(又想说说windows的远程线程创建了,可是说的话还要接着说其复杂的令牌机制,否则windows粉丝不服气,所以不说了罢),unix中建立进程使用fork,自然地这么一“叉子”就形成了自己的一个树枝,当然在自己眼线了,一般对于登录用户而言一个会话起始于一次login之后的shell,只要该用户不logout,在该终端的shell上执行的所有的非守护进程都是该shell的后代进程,因此它们组成一个会话,全部在shell的眼线中,一个会话终止于会话首长的death。现在考虑一下终端上的shell退出后的情景,按照规定,该终端上所有的进程都过继给了别的进程,大多数情况是init进程,然后紧接着另外一个用户登录了这个终端或者知道前一个登录用户密钥的另一个有不好念头的人登录了该终端,当然为其启动的shell创建了一个新的session,由于之前登录的用户已退出,现在登录的用户由于之前用户的进程组都成了孤儿进程组,所以它再有恶意也不能控制它们了,那些孤儿进程组中的成员要么继续安全的运行,要么被shell退出时发出的SIGHUP信号杀死。
       POSIX的规定是铁的纪律,而unix或者linux的不管是内核还是shell的实现则是一种遵守纪律的方式,铁的纪律要求作业控制要以session为基本,就是说不能操作别的session内的进程组,所以类似fg和bg等命令就不能操作孤儿进程,因此如果由于后台进程组由于读写终端被SIGSTOP信号停了,而后它又成了孤儿进程组的成员,那怎么办?别的session的作业控制命令又不能操作它,即使ps -xj找到了它然后手工发送了SIGCONT,那么它还是没法使用终端,这是POSIX的另一个纪律要求的,只有唯一和终端关联的session中的前台进程组的进程可以使用终端,因此只要有一个shell退出了,最好的办法就是将其session内的所有的进程都干掉,因此SIGHUP的原意就是如此,但是完全可以忽略这个信号或者自己定义对该信号的反应。POSIX的基本限制就是session的ID是不能设置的,因为它是受保护的基本单位,但是进程组的ID是可以设置的,毕竟它只是区分了不能的作业,最后进程的PID也是不能设置的,因为它是进程的内秉属性,形成树形进程结构的关键属性。
     POSIX对孤儿进程组的定义:组中没有一个进程的父进程和自己属于同一个会话但是不同进程组的。

转自 http://xingyunbaijunwei.blog.163.com/blog/static/765380672011112633634628/

posted @ 2012-12-16 10:42 鑫龙 阅读(343) | 评论 (0)编辑 收藏

1 什么时候需要同步?

2 在linux中, 线程同步机制有哪些?各适用在怎样的条件?进程同步有哪些?对应的数据类型、API 各有哪些?
在当前的POSIX标准中有三种线程同步机制,它们分别是:互斥量、读写锁、条件变量。
关于进程同步机制,POSIX定义了一种信号灯,而system V 定义了另外一种信号灯。
---------------------------------------------------------------------- 
线程同步

(1)、互斥量(mutex)

  互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。

  对互斥量进行加锁以后,任何其它试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。在这种情况下,每次只有一个线程可以向前执行。

pthread_mutex_t 
pthread_mutex_init();
pthread_mutex_lock();
pthread_mutex_unlock();
pthread_mutex_destroy();
(2)、读写锁

  读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。

  读写锁可以由三种状态:读模式下加锁状态写模式下加锁状态不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

  在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。

  读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。

  读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。

pthread_rwlock_t
pthread_rwlock_init();
pthread_rwlock_rdlock();
pthread_rwlock_wrlock();
pthread_rwlock_unlock();
pthread_rwlock_destroy();
(3) 、条件变量(condition)和监视器(monitor)

  条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。

  条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。

pthread_cond_t
pthread_cond_init();
pthreadf_cond_wait();
pthread_cond_signal();
pthread_cond_destroy();
----------------------------------------------------------------------
进程同步 
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

POSIX信号灯:
sem_t
sem_open();
sem_init();
sem_wait();
sem_post();
sem_close();
sem_unlink();
SysV信号灯:
semget();
semop();
semctl();
3 线程同步机制和进程同步机制有什么区别?哪个使用的范围更大?
显然进程同步机制可以用于线程同步,而线程同步机制不能用于进程同步。




posted @ 2012-12-14 11:46 鑫龙 阅读(472) | 评论 (0)编辑 收藏

线程的分离状态决定一个线程以什么样的方式来终止自己。
线程的默认属性,一般是非分离状态,
这种情况下,原有的线程等待创建的线程结束。
只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
程序员应该根据自己的需要,选择适当的分离状态。

 

 

关于分离线程的一种用法(转)

 

讲到分离线程,先得从僵尸进程讲起(抱歉,确实不知道线程是否有僵尸一说)。

关于僵尸进程:一般情况下进程终止的时候,和它相关的系统资源也并不是主动释放的,而是进入一种通常称为“僵尸”(zombie)的状态。它所占有 的资源一直被系统保留,直到它的父进程(如果它直接的父进程先于它去世,那么它将被init进程所收养,这个时候init就是它的父进程)显式地调用 wait系列函数为其“收尸”。为了让父进程尽快知道它去世的消息,它会在它死去的时候通过向父进程发送SIGCHLD信号的方式向其“报丧”。

所以一旦父进程长期运行,而又没有显示wait或者waitpid,同时也没处理SIGCHLD信号,这个时候init进程,就没办法来替子进程来收尸。这个时候,子进程就真的成了”僵尸“了。

同理:

如果一个线程调用了这个函数,那么当这个线程终止的时候,和它相关的系统资源将被自动释放,系统不用也不能用pthread_join()等待其退 出。有的时候分离线程更好些,因为它潜在地减少了一个线程回收的同步点,并且pthread_join()这个API确实也是相当地难用。

为了让主线程省去去子线程收尸的过程,可以使用

int pthread_detach(pthread_t thread);

来让子线程处于分离状态,就不需要父线程再pthread_join了。

我们来看一种分离线程的用法。上次别人问道一种情况,我发现必须要分离子线程:

void* task1(void*);

void usr();

int p1;

int main()
{
p1
=0;
usr();               
//调用这个认为是你的触发函数
    getchar();
return 1;
}

void usr()
{
pthread_t  pid1;
pthread_attr_t attr;
/*这里做你的事情*/
if(p1==0)
{    pthread_attr_init(
&attr);
pthread_attr_setdetachstate(
&attr, PTHREAD_CREATE_DETACHED);       //因为你的线程不便于等待的关系,设置为分离线程吧    
            pthread_create(&pid1, &attr, task1, NULL);

}

}

void* task1(void *arg1)
{
p1
=1;                           //让子线程不会被多次调用
    int i=0;
printf(
"thread1 begin./n");
for(i=0;i<100;i++)
{
sleep(
2);                  
printf(
"At thread1: i is %d/n",i);       
usr();                    
//继续调用
   }
pthread_exit();
}
 

我 们看到,在这里task1这个线程函数居然会多次调用其父线程里的函数,显然usr函数里,我们无法等待task1结束,反而task1会多次调用 usr,一旦我们在usr里pthread_join,则在子线程退出前,有多个usr函数会等待,很浪费资源。所以,此处,将task1设置为分离线程 是一种很好的做法。

posted @ 2012-12-14 11:01 鑫龙 阅读(194) | 评论 (0)编辑 收藏

这是一个网友的提问:

在 UNIX的system()函数实现过程中,要求在父进程中忽略掉SIGINT和SIGQUIT信号,但是要将SIGCHLD信号阻塞(在子进程中将 SIGINT和SIGQUIT信号设为默认,SIGCHLD信号解锁)。子进程执行完毕后,在父进程中调用waitpid(pid_t, &state, 0)。问题: 
1、若父进程已被waitpid阻塞,在子进程返回时,此时在父进程中SIGCHLD被阻塞(BLOCK),父进程收不到SIGCHLD信号,waitpid()函数能否正确返回,收集到子进程的信息? 
2、 waitpid若能正确完成,在以后父进程中,将SIGCHLD信号UNBLOCK,用sigprocmask()函数解锁,书上说,在 sigprocmask()函数返回以前,会将以前阻塞的信号发送给进程,父进程是否还能收到SIGCHLD信号?若能收到何必在开始时将SIGCHLD 进程阻塞。 


简单的对这个问题的解释是wait及其变体并不是通过sigchld信号来知道子进程状态的。
sigprocmask 阻塞的是有signal或sigaction设置的信号处理程序,即带有SIGCHLD_Handle()等处理函数。wait不是靠接收sigchld 信号获得子进程的退出状态的,如果进程中同时设置了signal和wait,则子进程退出后发出sigchld信号,交到signal的信号处理程序处 理,wait接收到子进程退出状态。
只是接收sigchld,而不调用wait还是会使子进程僵死的。一般的只有调用wait才能使子进程不成为僵死进程(除了2次fork 等或其他一些手段)。

概括下:waitpid不是依靠SIGCHLD是否到达来判断子进程是否退出,但是如果设置了SIGCHLD的处理函数,那么就需要等待SIGCHLD信号 的发生并完成信号处理函数,waitpid才能接收到子进程的退出状态。在APUE中的system()实现中阻塞了SIGCHLD信号,但是并没有设置 信号处理函数,所以waitpid在阻塞了SIGCHLD的情况下依然能正常返回,因为SIGCHLD在未设置信号处理函数的情况下不会影响到 waitpid的工作。至于为什么要阻塞SIGCHLD信号呢?那就是为了防止其他程序(main除了会调用system还会使用其他程序)设置了 SIGCHLD的信号处理函数,如果其他程序设置了SIGCHLD信号处理函数,在waitpid等待子程序的返回前,要去处理SIGCHLD信号处理程 序,如果阻塞了该信号,就不会去处理该信号处理程序,防止多余信息在system()中的出现。

posted @ 2012-12-14 10:58 鑫龙 阅读(357) | 评论 (0)编辑 收藏

早期的Unix系统,如果进程在一个‘慢’系统调用中阻塞时,捕获到一个信号,这个系统调用被中断,调用返回错误,设置errno为EINTR。系统调用被分为慢系统调用和其他两大类别。

    慢系统调用可以被永久阻塞,包括以下几个类别

       (1)读写‘慢’设备(包括pipe,终端设备,网络连接等)。读时,数据不存在,需要等待;写时,缓冲区满或其他原因,需要等待。读写磁盘文件一般不会阻塞。

       (2)当打开某些特殊文件时,需要等待某些条件,才能打开。例如:打开中断设备时,需要等到连接设备的modem响应才能完成。

       (3)pause和wait函数。pause函数使调用进程睡眠,直到捕获到一个信号。wait等待子进程终止。

       (4)某些ioctl操作。

       (5)某些IPC操作。

    有些情况下,即使操作被信号中断,还是要继续执行该操作,即需要重启该操作。那么,程序需要检查系统调用的错误类型是否为EINTR,如果是,表明系统调用被中断,则重新启动操作。典型代码如下所示:

     again:
          if ((n = read(fd, buf, BUFFSIZE)) < 0) {
             if (errno == EINTR)
                  goto again;     /* just an interrupted system call */
            /* handle other errors */
          }

    4.2BSD为了简化程序的操作,提供了自动重启某些被中断系统调用的功能,这些系统调用包括ioctl,read,readv,write,writev,wait,waitpid。前五个函数当它们操作慢设备时,才会被中断。这可能给那些不希望自动重启这些系统调用的应用带来麻烦,所以4.3BSD允许进程在指定信号上关闭此功能。

    POSIX.1允许实现重新启动系统调用,但没有强制要求。SUS给sigaction增加了一个XSI扩展标记SA_RESTART,要求被该信号中断的系统调用被自动重启。

 

别忘了--要处理被中断的系统调用

一般慢速系统调用基本规则是:当阻塞于某个慢系统系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能要返回

ENINTR错误。

 

问:linux会重启某些被中断的系统调用吗?

 

处理的例子:

for( ; ;) {
     if (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) 
    {
    if (errno == EINTR)
        continue;
    }
    else
    {
        errsys("accept error");
    }
}

 

在tcp socket 中,connect()被中断后是不能被重启的?如何处理呢

可以采用select来等待连接完成

 

系统调用被信号中断和自动重启动

    当进程正在执行一个系统调用时,如果被信号中断,这时会发生什么呢?

当一个低速调用阻塞期间捕捉到一个信号, 则该系统调用就被中断不再继续执行。 该系统调用返回出错,起errono设置为EINTR。 因为发生信号, 进程捕捉到它, 这将是一个很好的机会来唤醒阻塞的系统调用。

但有一个问题就是如果该系统调为read(), 正在等待终端输入, 如果被信号中断的话, 难免会影响整个程序的正确性, 所以有些系统使这类系统调用自动重启动。就是一旦被某信号中断, 立即再启动。

如下面的signal1函数实现: 

#include <signal.h>
#include "ourhdr.h"

typedef void Sigfunc(int);

Sigfunc *
signal1(int signo, Sigfunc *func)
{
struct sigaction        act, oact;

act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;

if (signo ==SIGALRM)
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;   /*这里对所有的信号都设置了自动再启动,唯独没有SIGALRM*/
#endif
}

if (sigaction(signo, &act, &oact)<0)
return(SIG_ERR);
return (oact.sa_handler);
}

为什么偏偏面对SIGALRM信号, 系统调用不设置自动重启动呢? 这时为了我们方便给read等低速系统调用定时。 我们不希望它一遇到某个信号变自动重启动,也不希望它无限制的阻塞下去。 于是用alarm()进行定时, 一旦超出某个时间, 便被ALRM信号中断唤醒,且不再重启动。


下面这段程序用来测试上面的signal1函数, 对一个read系统调用, 如何给它定时的:

#include <signal.h>
#include "ourhdr.h"
#include "10-12.c"

#define MAXLINE 1024

static void sig_alrm(int);

int
main(void)
{
int     n;
char line[MAXLINE];

if (signal1(SIGALRM, sig_alrm) == SIG_ERR)
perror("signal");

alarm(10);
if ( (n = read(STDIN_FILENO, line, MAXLINE)) < 0)
perror("read");
alarm(0);
write(STDOUT_FILENO, line, n);
write(STDOUT_FILENO, "exit\n", 5);

exit(0);
}

static void
sig_alrm(int signo)
{
write(STDOUT_FILENO, "recieved signal -ALRM\n", 22);
return;
}

在我的系统中, 如果调用默认的signal函数, 该read()系统调用将会自动重启动, 所谓的alarm定时也就不起作用了。

posted @ 2012-12-10 16:51 鑫龙 阅读(745) | 评论 (2)编辑 收藏

     摘要:        在UNIX系统中,作业控制:允许在一个终端上启动多个作业(进程组),控制哪一个作业可以存取该终端,以及哪些作业在后台运行。一句话就是,作业控制是解决不同作业(也就是进程组)对控制终端这个资源的使用的竞争问题。作业控制作为Shell的一个特性存在,也就是说有的shell支持作业控制这个作业功能,有的不支持。linux下常用的bash是支持的作业控...  阅读全文

posted @ 2012-12-09 14:30 鑫龙 阅读(226) | 评论 (0)编辑 收藏

如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求的技巧是调用fork2次。

下面是实例代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(void)
{
        pid_t pid;

        if((pid = fork()) < 0) {
                printf("error: fork error.\n");
        } else if(pid == 0) {
                if((pid = fork()) < 0)
                        printf("error: fork error.\n");
                else if(pid > 0)
                        exit(0);
                /* we are the second child; our parent becomes init as soon as
                 * our real parent calls exit() in the statement above. Here is 
                 * where we had continue executing , knowing that when we are 
                 * done, init will reap our status. 
                 
*/
                sleep(2);
                printf("second child, parent pid = %d\n", getppid());
                exit(0);
        }

        if(waitpid(pid, NULL, 0) != pid)
                printf("error, waitpid error.\n");

        exit(0);
}
      这里核心思想是,把第二个子进程的父进程换成init进程,因为init进程会立马终止僵死进程。而最开始的父进程也因为直接子进程(第一个进程)终止,不需要阻塞。
      第二个子进程调用sleep以保证在打印父进程ID时第一个字进程已终止。在fork之后,父子进程都可以继续执行,并且我们无法预知哪个会限制性。在fork之后,如果不是第二个子进程休眠,那么它可能比其父进程先执行,于是它打印的父进程ID将是创建它的父进程,而不是init进程。

posted @ 2012-12-02 18:12 鑫龙 阅读(349) | 评论 (0)编辑 收藏

最近一直在看STL和Boost,源码里边好多涉及到模板元编程技术,简单了解一下,备忘(Boost Python中的涉及模板元的部分重点关注一下)。

范例引入

// 主模板
template<int N>
struct Fib
{
    
enum { Result = Fib<N-1>::Result + Fib<N-2>::Result };
};

// 完全特化版
template <>
struct Fib<1>
{
    
enum { Result = 1 };
};


// 完全特化版
template <>
struct Fib<0>
{
    
enum { Result = 0 };
};

int main()
{
    
int i = Fib<10>::Result;
    
// std::cout << i << std::endl;
}

主要思想

利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。

优劣及适用情况

通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。也就是说模板元编程的优势在于:

  1.以编译耗时为代价换来卓越的运行期性能(一般用于为性能要求严格的数值计算换取更高的性能)。通常来说,一个有意义的程序的运行次数(或服役时间)总是远远超过编译次数(或编译时间)。

  2.提供编译期类型计算,通常这才是模板元编程大放异彩的地方。

模板元编程技术并非都是优点:

  1.代码可读性差,以类模板的方式描述算法也许有点抽象。

  2.调试困难,元程序执行于编译期,没有用于单步跟踪元程序执行的调试器(用于设置断点、察看数据等)。程序员可做的只能是等待编译过程失败,然后人工破译编译器倾泻到屏幕上的错误信息。

  3.编译时间长,通常带有模板元程序的程序生成的代码尺寸要比普通程序的大,

  4.可移植性较差,对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。

总结:

模板元编程技术不适用普通程序员的日常应用,它常常会做为类库开发的提供技术支持,为常规模板代码的内核的关键算法实现更好的性能或者编译期类型计算。模板元程序几乎总是应该与常规代码结合使用被封装在一个程序库的内部。对于库的用户来说,它应该是透明的。

工程应用实例

1. Blitz++:由于模板元编程最先是因为数值计算而被发现的,因此早期的研究工作主要集中于数值计算方面,Blitz++库利用模板将运行期计算转移至编译期的库,主要提供了对向量、矩阵等进行处理的线性代数计算。

2.Loki:将模板元编程在类型计算方面的威力应用于设计模式领域,利用元编程(以及其他一些重要的设计技术)实现了一些常见的设计模式之泛型版本。Loki库中的Abstract Factory泛型模式即借助于这种机制实现在不损失类型安全性的前提下降低对类型的静态依赖性。

3.Boost:元编程库目前主要包含MPL、Type Traits和Static Assert等库。 Static Assert和Type Traits用作MPL的基础。Boost Type Traits库包含一系列traits类,用于萃取C++类型特征。另外还包含了一些转换traits(例如移除一个类型的const修饰符等)。Boost Static Assert库用于编译期断言,如果评估的表达式编译时计算结果为true,则代码可以通过编译,否则编译报错。

技术细节

模板元编程使用静态C++语言成分,编程风格类似于函数式编程,在模板元编程中,主要操作整型(包括布尔类型、字符类型、整数类型)常量和类型,不可以使用变量、赋值语句和迭代结构等。被操纵的实体也称为元数据(Metadata),所有元数据均可作为模板参数。

由于在模板元编程中不可以使用变量,我们只能使用typedef名字和整型常量。它们分别采用一个类型和整数值进行初始化,之后不能再赋予新的类型或数值。如果需要新的类型或数值,必须引入新的typedef名字或常量。

其它范例


// 仅声明
struct Nil;

// 主模板
template <typename T>
struct IsPointer
{
    
enum { Result = false };
    typedef Nil ValueType;
};

// 局部特化
template <typename T>
struct IsPointer<T*>
{
    
enum { Result = true };
    typedef T ValueType;
};

// 示例
int main()
{
    cout 
<< IsPointer<int*>::Result << endl;
    cout 
<< IsPointer<int>::Result << endl;
    IsPointer
<int*>::ValueType i = 1;
    
//IsPointer<int>::ValueType j = 1;  
    
// 错误:使用未定义的类型Nil
}


//主模板
template<bool>
struct StaticAssert;

// 完全特化
template<> 
struct StaticAssert<true>
{};

// 辅助宏
#define STATIC_ASSERT(exp)\
{ StaticAssert
<((exp) != 0)> StaticAssertFailed; }

int main()
{
    STATIC_ASSERT(
0>1);
}
 

 

References:

http://club.topsage.com/thread-421469-1-1.html

http://wenku.baidu.com/view/c769720df78a6529647d539d.html

Blitz++: http://www.oonumerics.org/blitz .
Loki :http://sourceforge.net/projects/loki-lib

Boost:http://www.boost.org/

posted @ 2012-12-01 20:01 鑫龙 阅读(306) | 评论 (0)编辑 收藏

      什么是traits,为什么人们把它认为是C++ Generic Programming的重要技术?

      简洁地说,traits如此重要,是因为此项技术允许系统在编译时根据类型作一些决断,就好像在运行时根据值来做出决断一样。更进一步,此技术遵循“另增一个间接层”的谚语,解决了不少软件工程问题,traits使您能根据其产生的背景(context) 来做出抉择。这样最终的代码就变得清晰易读,容易维护。如果你正确运用了traits技术,你就能在不付出任何性能和安全代价的同时得到这些好处,或者能够契合其他解决方案上的需求。
      先举个浅显易懂的例子来说明traits的用法:
//首先假如有以下一个泛型的迭代器类,其中类型参数 T 为迭代器所指向的类型:
template <typename T>
class myIterator
{

};
       那么当使用myIterator时,怎样才能知道它所指向元素的类型呢?一种解决方案是为这个类加入一个内嵌类型:
template <typename T>
class myIterator
{
typedef T value_type;

};
      当使用myIterator时,可以通过myIterator::value_type来获得相应的myIterator所指向的类型。下面举例使用:
template <typename T>
typename myIterator<T>::value_type func(myIterator<T> i)
{

}
      这里定义了一个函数func,返回值类型为参数i所指的类型,也就是模板参数T,那么为什么不直接使用模板参数T,而要绕着圈去使用那个value_type呢?所以我们返回来,当修改func函数时,它能够适应所有类型的迭代器,不是更好吗?如下所示:
template <typename I> //这里的I可以是任意类型的迭代器
typename I::value_type func(I i)
{

}
      现在,任意定义了value_type内嵌类型的迭代器都可以做为func的参数了,并且func的返回值的类型将与相应迭代器所指的元素的类型一致。至此一切问题似乎都已解决,并且似乎并没有使用任何特殊的技术。
      然而当考虑到以下情况时,新的问题便显现出来了:原生指针也完全可以做为迭代器来使用,然而显然没有办法为原生指针添加一个value_type的内嵌类型,如此一来func()函数就不能适用原生指针了,这不能不说是一大缺憾。那么有什么办法可以解决这个问题呢?此时不禁想到了用Traits萃取类型信息。可以不直接使用myIterator的value_type,而是通过Traits类来把这个信息提取出来:(不同的类型,可以有不同的提取方式)
template <typename T>
class Traits
{
typedef typename T::value_type value_type;
};
      这样以后就可以通过Traits<myIterator>::value_type来提取出myIterator中的value_type,于是func函数改写成:
template <typename I> //这里的I可以是任意类型的迭代器
typename Traits<I>::value_type Foo(I i)
{

}
      然而,即使这样,那个原生指针的问题仍然没有解决,因为Trait类还是没办法获得原生指针的相关信息。于是不妨将Traits偏特化(partial specialization):(通过特化、重载特化等手段产出不同的提取方式)
template <typename T>
class Traits<T*> //注意 这里针对原生指针进行了偏特化
{
typedef typename T value_type;
};
     通过上面这个Traits的偏特化版本,一个T*类型的指针所指向的元素的类型为T。如此一来,我们func函数就完全可以适用于原生指针了。比如:
int * p;
.
int i = func(p);
     Traits会自动推导出p所指元素的类型为int,从而func正确返回。
-----------------------------------------------------------------------------------------------------------------------------------------------------------
     现在再看一个更加一般的例子——smart pointers。(智能指针)
     假设你正在设计一个SmartPtr模板类,对于一个smart pointer 来说,它的最大的用处是可以自动管理内存问题,同时在其他方面又像一个常规指针。但是有些C++的Smart pointer实现技术却非常令人难以理解。这一残酷的事实带来了一个重要实践经验:你最好尽一切可能一劳永逸,写出一个出色的、具有工业强度的 smart pointer来满足你所有的需求。此外,你通常不能修改一个类来适应你的smart pointer,所以你的SmartPtr一定要足够灵活。
     有不少类层次使用引用计数(reference counting)以及相应的函数管理对象的生存期。然而,并没有reference counting的标准实现方法,每一个C++库的供应商在实现的语法和/或语义上都有所不同。例如,在你的应用程序中有这样两个interfaces:
第一种智能指针--大部分的类实现了RefCounted接口:
class RefCounted
{
public:
virtual void IncRef() = 0;
virtual bool DecRef() = 0;
// if you DecRef() to zero references, the object is destroyed
// automatically and DecRef() returns true
virtual ~RefCounted() {}
};
第二种智能指针--第三方提供的Widget类使用不同的接口:
class Widget
{
public:
void AddReference();
int RemoveReference(); 
// returns the remaining number of references; it's the client's
// responsibility to destroy the object

};
      不过你并不想维护两个smart pointer类,你想让两种类共享一个SmartPtr。一个基于traits的解决方案把两种不同的接口用语法和语义上统一的接口包装起来,建立针对普通类的通用模板,而针对Widget建立一个特殊化版本,如下:
template <class T>
class RefCountingTraits
{
static void Refer(T* p)
{
p->IncRef(); // assume RefCounted interface
}
static void Unrefer(T* p)
{
p->DecRef(); //assume RefCounted interface
}
};
template<>
class RefCountingTraits<Widget>
{
static void Refer(Widget* p)
{
p->AddReference(); //use Widget interface
}
static void Unrefer(Widget* p)
{
//use Widget interface
If (p->RemoveReference() == 0)
delete p;
}
};
      在SmartPtr里,我们像这样使用RefCountingTraits:
template <class T>
class SmartPtr
{
private:
typedef RefCountingTraits<T> RCTraits;
T* pointee_;
public:

~SmartPtr()
{
RCTraits::Unrefer(pointee_);
}
};
      当然在上面的例子里,你可能会争论说你可以直接特殊化Widget类的SmartPtr的构造与析构函数。你可以使用把模板特殊化技术用在 SmartPtr本身,而不是用在traits上头,这样还可以消除额外的类。尽管对这个问题来说这种想法没错,但还是由一些你需要注意的缺陷:
  1. 这么干缺乏可扩展性。如果给SmartPtr再增加一个模板参数,你不能特殊化这样一个SmartPtr<T. U>,其中模板参数T是Widget,而U可以为其他任何类型。
  2. 最终代码不那么清晰。Trait有一个名字,而且把相关的东西很好的组织起来,因此使用traits的代码更加容易理解。相比之下,用直接特殊化SmartPtr成员函数的代码,看上去更招黑客的喜欢。
      用继承机制的解决方案,就算本身完美无瑕,也至少存在上述的缺陷。解决这样一个变体问题,使用继承实在是太笨重了。此外,通常用以取代继承方案的另一种经典机制——containment,用在这里也显得画蛇添足,繁琐不堪。相反,traits方案干净利落,简明有效,物合其用,恰到好处。
      Traits的一个重要的应用是“interface glue”(接口胶合剂),通用的、可适应性极强的适配子。如果不同的类对于一个给定的概念有着不同的实现,traits可以把这些实现再组织统一成一个公共的接口。对于一个给定类型提供多种TRAITS:现在,我们假设所有的人都很喜欢你的SmartPtr模板类,直到有一天,在你的多线程应用程序里开始现了神秘的bug。你发现罪魁祸首是Widget,它的引用计数函数并不是线程安全的。现在你不得不亲自实现Widget:: AddReference和Widget::RemoveReference,最合理的位置应该是在RefCountingTraits中,打上个补丁吧:
// Example 7: Patching Widget's traits for thread safety
template <>
class RefCountingTraits<Widget>
{
static void Refer(Widget* p)
{
Sentry s(lock_); // serialize access
p->AddReference();
}
static void Unrefer(Widget* p)
{
Sentry s(lock_); // serialize access
if (p->RemoveReference() == 0)
delete p;
}
private:
static Lock lock_;
};
       不幸的是,虽然你重新编译、测试之后正确运行,但是程序慢得像蜗牛。仔细分析之后发现,你刚才的所作所为往程序里塞了一个糟糕的瓶颈。实际上只有少数几个Widget是需要能够被好几个线程访问的,余下的绝大多数Widget都是只被一个线程访问的。你要做的是告诉编译器按你的需求分别使用多线程traits和单线程traits这两个不同版本。你的代码主要使用单线程traits。
       如何告诉编译器使用哪个traits?一种方法是把traits作为另一个模板参数传给SmartPtr。缺省情况下传递老式的traits模板,而用特定的类型实例化特定的模板。
template <class T, class RCTraits = RefCountingTraits<T> >
class SmartPtr
{

};
      你对单线程版的RefCountingTraits<Widget>不做改动,而把多线程版放在一个单独的类中:
class MtRefCountingTraits
{
static void Refer(Widget* p)
{
Sentry s(lock_); // serialize access
p->AddReference();
}
static void Unrefer(Widget* p)
{
Sentry s(lock_); // serialize access
if (p->RemoveReference() == 0)
delete p;
}
private:
static Lock lock_;
};
       现在你可将SmartPtr<Widget>用于单线程目的,将SmartPtr<Widget,MtRefCountingTraits>用于多线程目的。
       最后,以SGI STL中的__type_traits结束本篇讨论,在SGI 实现版的STL中,为了获取高效率,提供了__type_traits,用来提取类的信息,比如类是否拥有trival的构造、析构、拷贝、赋值操作,然后跟据具体的信息,就可提供最有效率的操作。以下摘录cygwin的gcc3.3源码,有改动,在<type_traits.h>中。
struct __true_type {};
struct __false_type {};
template <class _Tp>
struct __type_traits
{
typedef __true_type    this_dummy_member_must_be_first;
typedef __false_type    has_trivial_default_constructor;
typedef __false_type    has_trivial_copy_constructor;
typedef __false_type    has_trivial_assignment_operator;
typedef __false_type    has_trivial_destructor;
typedef __false_type    is_POD_type;
};
        对于普通类来讲,为了安全起见,都认为它们拥有non-trival的构造、析构、拷贝、赋值函数,POD是指plain old data。接下来对C++的原生类型(bool,int, double之类)定义了显式的特化实现,以double为例:
template<> 
struct __type_traits<long double> {
typedef __true_type    has_trivial_default_constructor;
typedef __true_type    has_trivial_copy_constructor;
typedef __true_type    has_trivial_assignment_operator;
typedef __true_type    has_trivial_destructor;
typedef __true_type    is_POD_type;
};
还有,对所有的原生指针来讲,它们的构造、析构等操作也是trival的,因此有:
template <class _Tp>
struct __type_traits<_Tp*> {
typedef __true_type    has_trivial_default_constructor;
typedef __true_type    has_trivial_copy_constructor;
typedef __true_type    has_trivial_assignment_operator;
typedef __true_type    has_trivial_destructor;
typedef __true_type    is_POD_type;
};
      简化<stl_algobase.h>中copy的部分代码来说明对__type_traits的应用。
template<typename _Tp>
inline _Tp* __copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result)
{
memmove(__result, __first, sizeof(_Tp) * (__last - __first));
return __result + (__last - __first);
}
template<typename _Tp>
inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __true_type)
return __copy_trivial(__first, __last, __result); } 
template<typename _Tp>
inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __false_type)
{ 另外处理;}
template<typename _InputIter, typename _OutputIter> inline 
_OutputIter copy (_InputIter __first, _InputIter __last, _OutputIter __result)
{
typedef typename iterator_traits<_InputIter>::value_type _ValueType;
typedef typename __type_traits<_ValueType>::has_trivial_assignment_operator _Trivial;
return __copy_aux(__first, __last, __result, _Trivial());
}
      Copy 函数利用__type_traits判断当前的value_type是否有trival的赋值操作,如果是,则产生类__true_type的实例,编译 时选择__copy_trivial函数进行memmove,效率最高。如果是non-trival的赋值操作,则另作处理,效率自然低些。__true_type和__false_type之所以是类,就因为C++的函数重载是根据类型信息来的,不能依据参数值来判别。使用SGI STL时,可以为自己的类定义__type_traits显式特化版本,以求达到高效率。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/budTang/archive/2008/05/06/2397013.aspx

posted @ 2012-12-01 19:57 鑫龙 阅读(656) | 评论 (0)编辑 收藏

仅列出标题
共20页: First 10 11 12 13 14 15 16 17 18 Last