Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3533917
  • 博文数量: 864
  • 博客积分: 14125
  • 博客等级: 上将
  • 技术积分: 10634
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-27 16:53
个人简介

https://github.com/zytc2009/BigTeam_learning

文章分类

全部博文(864)

文章存档

2023年(1)

2021年(1)

2019年(3)

2018年(1)

2017年(10)

2015年(3)

2014年(8)

2013年(3)

2012年(69)

2011年(103)

2010年(357)

2009年(283)

2008年(22)

分类: LINUX

2010-10-28 16:22:03

进程间通信程序设计

为什么进程间需要通信?

1、数据传输

一个进程需要将它的数据发送给另一个进程。

2、资源共享

多个进程之间共享同样的资源。

3、通知事件

一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。

4、进程控制

有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变。

 

Linux进程间通信(IPC)由以下几部分发展而来:

1UNIX进程间通信

2、基于System V进程间通信

3POSIX进程间通信

POSIX(Portable Operating System Interface)表示可移植操作系统接口。电气和电子工程师协会(Institute of Electrical and Electronics EngineersIEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX,许多其它的操作系统,例如 DEC OpenVMS Microsoft Windows,都支持 POSIX 标准。

System V,也被称为 AT&T System V,是Unix操作系统众多版本中的一支。

 分类

现在Linux使用的进程间通信方式包括:

1、管道(pipe)和有名管道(FIFO

2、信号(signal

3、消息队列

4、共享内存

5、信号量

6、套接字(socket

管道通信

什么是管道?

管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。

数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞。

 

管道创建

管道包括无名管道有名管道两种,前者用于父进程和子进程间的通信,后者可用于运行于同一系统中的任意两个进程间的通信。

无名管道由pipe()函数创建:

int pipe(int filedis[2])

当一个管道建立时,它会创建两个文件描述符filedis[0] 用于读管道, filedis[1] 用于写管

道。

 

管道关闭

关闭管道只需将这两个文件描述符关闭即可,可以使用普通的close函数逐个关闭。

 

#include

#include

#include

#include

 

int main(){

      int pipe_fd[2];

      if(pipe(pipe_fd)<0){

           printf("pipe create error\n");

           return -1;

      }     

      else

           printf("pipe create success\n");

      close(pipe_fd[0]);

      close(pipe_fd[1]);

}

 

管道读写

管道用于不同进程间通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。

注意事项

必须在系统调用fork( )前调用pipe( ),否则子进程将不会继承文件描述符。

 

实例分析

#include

#include

#include

#include

#include

int main(){

      int pipe_fd[2];

      pid_t pid;

      char buf_r[100];

      char *p_wbuf;

      int r_num;     

      memset(buf_r,0,sizeof(buf_r));     

      /*创建管道*/

      if(pipe(pipe_fd)<0){

           printf("pipe create error\n");

           return -1;

      }

     

      /*创建子进程*/

      if((pid=fork())==0){

           printf("\n");

           close(pipe_fd[1]);

           sleep(2);

           if((r_num=read(pipe_fd[0],buf_r,100))>0){

                 printf(   "%d numbers read from the pipe is %s\n",r_num,buf_r);

           }

           close(pipe_fd[0]);

           exit(0);

      }

      else if(pid>0){

           close(pipe_fd[0]);

           if(write(pipe_fd[1],"Hello",5)!=-1)

                 printf("parent write1 hello!\n");

           if(write(pipe_fd[1]," Pipe",5)!=-1)

                 printf("parent write2 Pipe!\n");

           close(pipe_fd[1]);

           sleep(3);

           waitpid(pid,NULL,0);

           exit(0);

      }

      return 0;

      }

命名管道(FIFO

命名管道和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过命名管道,不相关的进程也能交换数据。

创建

#include

#include

int mkfifo(const char * pathname, mode_t mode)

pathnameFIFO文件名

mode:属性(见文件操作

一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(closereadwrite等)都可用于FIFO。)

操作

当打开FIFO时,非阻塞标志(O_NONBLOCK)将对以后的读写产生如下影响:

1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞。

2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回,errnoENXIO

 

实例fifo_write.c

#include

#include

#include

#include

#include

#include

#include

#define FIFO_SERVER "/tmp/myfifo"

 

main(int argc,char **argv){

      int fd;

      char w_buf[100];

      int nwrite;     

      /*打开管道*/

      fd = open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

      if(argc==1){

           printf("please send something|n");

           exit(-1);

      }     

      strcpy(w_buf, argv[1]);

     

      /* 向管道写入数据 */

      if((nwrite=write(fd,w_buf,100))==-1){

           if(errno==EAGAIN)

                 printf("The FIFO has not been read yet.Please try later\n");

      }

      else

           printf("write %s to the FIFO\n",w_buf);   

}

 

实例fifo_read.c

#include

#include

#include

#include

#include

#include

#include

#define FIFO_SERVER "/tmp/myfifo"

 

main(int argc,char **argv){

      char buf_r[100];

      int  fd;

      int  nread;

     

      /* 创建管道 */

      if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))

           printf("cannot create fifoserver\n");

     

      printf("preparing for reading bytes...");

     

      memset(buf_r,0,sizeof(buf_r));

          

      /*打开管道*/

      fd = open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

      if(fd==-1){

           printf("open");

           exit(-1);

      }

     

      while(1){

           memset(buf_r,0,sizeof(buf_r));

          

           if((nread=read(fd,buf_r,100))==-1){

                 if(errno==EAGAIN)

                      printf("no data yet\n");

           }

           printf("read %s from FIFO\n",buf_r);

           sleep(1);

      }

      pause();/*暂停,等待信号*/

      unlink(FIFO_SERVER);//删除文件

}

信号通讯

信号(signal)机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:

1、当用户按某些按键时,产生信号。

2、硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正访问一个无效存储区的进程产生一个SIGSEGV信号。

3、进程用kill函数将信号发送给另一个进程。

4、用户可用kill命令将信号发送给其他进程。

 

信号类型

1) SIGHUP  2) SIGINT  3) SIGQUIT  4) SIGILL 5) SIGTRAP 6) SIGIOT  7) SIGBUS  8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD  18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN      22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH  29) SIGIO 30) SIGPWR

 

下面是几种常见的信号:

 SIGHUP:从终端上发出的结束信号

 SIGINT:来自键盘的中断信号(Ctrl-C

 SIGKILL:该信号结束接收信号的进程

 SIGTERMkill 命令发出的信号

 SIGCHLD:标识子进程停止或结束的信号

 SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号

 

信号处理

当某信号出现时,将按照下列三种方式中的一种进行处理:

1、忽略此信号

大多数信号都按照这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILLSIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了一种终止或停止进程的方法。

2、执行用户希望的动作

通知内核在某种信号发生时,调用一个用户函数。在用户函数中,执行用户希望的处理。

3、执行系统默认动作

对大多数信号的系统默认动作是终止该进程。

 

发送信号的主要函数有 killraise

区别:

Kill既可以向自身发送信号,也可以向其他进程发送信号。与kill函数不同的是,raise函数是向进程自身发送信号。

#include

#include

int kill(pid_t pid, int signo)

int raise(int signo)

killpid参数有四种不同的情况:

1pid>0

将信号发送给进程IDpid的进程。

2pid == 0

将信号发送给同组的进程。

3pid < 0

将信号发送给其进程组ID等于pid绝对值的进程。

4pid ==1

将信号发送给所有进程。

 

Alarm

使用alarm函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生SIGALRM信号。如果不捕捉此信号,则默认动作是终止该进程。

#include

unsigned int alarm(unsigned int seconds)

Seconds

经过了指定的seconds秒后会产生信号SIGALRM

v每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换。

v如果有以前登记的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟。

pause函数使调用进程挂起直至捕捉到一个信号。

#include

int pause(void)

只有执行了一个信号处理函数后,挂起才结束。

v 当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号,或者使用系统默认的方式。

v 信号处理的主要方法有两种,一种是使用简单的signal函数,另一种是使用信号集函数组。

 

#include

void (*signal (int signo, void (*func)(int)))(int)

如何理解?

typedef void (*sighandler_t)(int)

sighandler_t signal(int signum, sighandler_t handler))

Func可能的值是:

1SIG_IGN:忽略此信号

2SIG_DFL: 按系统默认方式处理

3、信号处理函数名:使用该函数处理

 

实例

#include

#include

#include

 

void my_func(int sign_no){

      if(sign_no==SIGINT)

           printf("I have get SIGINT\N");

      else if(sign_no==SIGQUIT)

           printf("I have get SIGQUIT\n");

}

 

int main(){

      printf("Waiting for signal SIGINT or SIGQOIT \n");

     

      /*注册信号处理函数*/

      signal(SIGINT, my_func);

      signal(SIGQUIT, my_func);

     

      pause();

      exit(0);

}

 

 

共享内存

共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。

共享内存实现分为两个步骤:

一、创建共享内存,使用shmget函数。

二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。

 

创建

int shmget ( key_t key, int size, int shmflg )

key标识共享内存的键值: 0/IPC_PRIVATE key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。

返回值:如果成功,返回共享内存标识符;如果失败,返回-1

映射

int shmat ( int shmid, char *shmaddr, int flag)

参数:

vshmidshmget函数返回的共享存储标识符

vflag:决定以什么方式来确定映射的地址(通常为0

返回值:

如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1

 

当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。

int shmdt ( char *shmaddr )

 

实例

#include

#include

#include

#include

#include

#include

#include

#include

#include

 

#define PERM S_IRUSR|S_IWUSR

/* 共享内存 */

 

int main(int argc,char **argv)

{

      int shmid;

      char *p_addr,*c_addr;

     

      if(argc!=2)

      {

           fprintf(stderr,"Usage:%s\n\a",argv[0]);

           exit(1);

      }

 

      /* 创建共享内存 */ 

      if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)

      {

           fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno));

           exit(1);

      }

 

      /* 创建子进程 */

      if(fork()) // 父进程写

      {

           p_addr=shmat(shmid,0,0);

           memset(p_addr,'\0',1024);

           strncpy(p_addr,argv[1],1024);

           wait(NULL); // 释放资源,不关心终止状态

           exit(0);

      }

      else       // 子进程读

      {

           sleep(1); // 暂停1     

           c_addr=shmat(shmid,0,0);

           printf("Client get %s\n",c_addr);

           exit(0);

      }

}

 

 

消息队列

unix早期通信机制之一的信号能够传送的信息量有限,管道则只能传送无格式的字节流,这无疑会给应用程序开发带来不便。消息队列(也叫做报文队列)则克服了这些缺点。

消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式。进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息。

目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。

系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。

消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值。

 

键值

#include

#include

key_t ftok (char*pathname, char proj)

功能:

返回文件名对应的键值。

pathname:文件名

proj:项目名(不为0即可)

 

打开/创建

#include

#include

#include

int msgget(key_t key, int msgflg)

key:键值,由ftok获得。

msgflg:标志位。

返回值:与健值key相对应的消息队列描述字。

IPC_CREAT

创建新的消息队列

IPC_EXCL

IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。

IPC_NOWAIT

读写消息队列要求无法得到满足时,不阻塞。

 

在以下两种情况下,将创建一个新的消息队列:

如果没有与健值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。

key参数为IPC_PRIVATE

创建

int open_queue(key_t keyval){

int qid;

if((qid=msgget(keyval,IPC_CREAT))==-1){

return(-1);

}

return(qid);

}

 

发送消息

#include

#include

#include

int msgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg)

功能:向消息队列中发送一条消息。

msqid

已打开的消息队列id

 msgp

存放消息的结构

 msgsz

消息数据长度

 msgflg

发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。

 

消息格式

struct msgbuf {

long mtype; /* 消息类型 > 0 */

char mtext[1];  /* 消息数据的首地址 */

};

 

接收消息

#include

#include

#include

int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)

功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。

 

int read_message(int qid,long type,struct mymsgbuf*qbuf)

{

int result,length;

length=sizeof(struct mymsgbuf)-sizeof(long);

if((result=msgrcv(qid,qbuf,length,type,0))==-1)

return(-1);

return(result);

}

 

信号量

信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步

 

分类

二值信号灯:信号灯的值只能取01,类似于互斥锁。 但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。

计数信号灯:信号灯的值可以取任意非负值。

 

创建 打开

#include

#include

#include

int semget(key_t key, int nsems, int semflg)

key:键值,由ftok获得

nsems:指定打开或者新创建的信号灯集中将包含信号灯的数目

semflg:标识,同消息队列

 

操作

int semop(int semid, struct sembuf *sops, unsigned nsops)

功能:对信号量进行控制。

semid:信号量集的ID

sops:是一个操作数组,表明要进行什么操作

nsopssops所指向的数组的元素个数。

 

struct sembuf {

unsigned short sem_num; /* semaphore

index in array */

short sem_op; /* semaphore operation */

short sem_flg; /* operation flags */

};

 

sem_num:要操作的信号量在信号量集中的编号,第一个信号的编号是0sem_op:如果其值为正数,该值会加到现有的信号量值中,通常用于释放信号量;如果sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值,通常用于获取信号量;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0

Sem_flg:信号操作标志,可能的选择有两种:

 IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。

 IPC_UNDO:程序结束时(不论正常或不正常)释放信号量,这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

多线程序设计

线程理论基础

线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NTLinux

使用多线程的理由之一是:

和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。

使用多线程的理由之二是:

线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。

除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:

v使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

v改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

 

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a

 

创建线程

#include

int pthread_create(pthread_t * tidp,const pthread_attr_t *attr, void *(*start_rtn)(void),void *arg)

tidp:线程id

attr:线程属性(通常为空)

start_rtn:线程要执行的函数

argstart_rtn的参数

因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread

# gcc filename –lpthread

 

终止线程

如果进程中任何一个线程中调用exit_exit,那么整个进程都会终止。线程的正常退出方式有:

(1) 线程从启动例程中返回

(2) 线程可以被另一个进程终止

(3) 线程自己调用pthread_exit函数

#include

void pthread_exit(void * rval_ptr)

功能:终止调用线程

Rval_ptr:线程退出返回值的指针。

 

线程等待

#include

int pthread_join(pthread_t tid,void **rval_ptr)

功能:阻塞调用线程,直到指定的线程终止。

Tid :等待退出的线程id

Rval_ptr:线程退出的返回值的指针

 

线程标识

#include

pthread_t pthread_self(void)

功能:

获取调用线程的 thread identifier

 

清除

线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,如何保证线程终止时能顺利的释放掉自己所占用的资源,是一个必须考虑解决的问题。

pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。

 

#include

void pthread_cleanup_push(void (*rtn)(void *),void *arg)

功能:

将清除函数压入清除栈

Rtn:清除函数

Arg:清除函数的参数

#include

void pthread_cleanup_pop(int execute)

功能:

将清除函数弹出清除栈

参数:

Execute执行到pthread_cleanup_pop()时是否在弹出清

理函数的同时执行该函数,非0:执行; 0:不执行

 

线程同步

进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决线程之间对资源的竞争:

1 互斥量Mutex

2 信号灯Semaphore

3 条件变量Conditions

互斥量

Item * p =queue_list;

Queue_list=queue_list->next;

process_job(p);

free(p);

当线程1处理完Item *p=queue_list后,系统停止线程1的运行,改而运行线程2。线程2照样取出头节点,然后进行处理,最后释放了该节点。过了段时间,线程1重新得到运行。而这个时候,p所指向的节点已经被线程2释放掉,而线程1对此毫无知晓。他会接着运行process_job(p)。而这将导致无法预料的后果!

对于这种情况,系统给我们提供了互斥量。线程在取出头节点前必须要等待互斥量,如果此时有其他线程已经获得该互斥量,那么该线程将会阻塞在这里。只有等到其他线程释放掉该互斥量后,该线程才

有可能得到该互斥量。互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。

Linux, 互斥量使用类型pthread_mutex_t表示。在使用前, 要对它进行初始化:

v对于静态分配的互斥量, 可以把它设置为默认的mutex对象PTHREAD_MUTEX_INITIALIZER

v对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy

 

#include

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

 int pthread_mutex_destroy(pthread_mutex_t *mutex)

 

加锁

对共享资源的访问, 要使用互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁。

 int pthread_mutex_lock(pthread_mutex_t *mutex)

 int pthread_mutex_trylock(pthread_mutex_t *mutex)

返回值: 成功则返回0, 出错则返回错误编号。

trylock是非阻塞调用模式, 如果互斥量没被锁住, trylock函数将对互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了,

trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。

 

解锁

在操作完成后,必须给互斥量解锁,也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞。

int pthread_mutex_unlock(pthread_mutex_t *mutex)

 

互斥量 信号量

 Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。

 Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore

 Binary semaphoreMutex的差异:

1. mutex要由获得锁的线程来释放(谁获得,谁释放)。而semaphore可以由其它线程释放

2. 初始状态可能不一样:mutex的初始值是1 ,semaphore的初始值可能是0(或者为1)。

 

厕所理论

Mutex:

Is a key to a toilet. One person can have the key - occupy the toilet - at the time. When finished, the person gives (frees) the key to the next person in the queue.Officially: Mutexes are typically used to serialise access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A mutex object only allows one thread into a controlled section, forcing other threads which attempt to gain access to that section to wait until the first thread has exited from that section.”Ref: Symbian Developer Library(A mutex is really a semaphore with value 1.)

Semaphore:

Is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count - the count of keys - is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.

Officially: “A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore).” Ref: Symbian Developer Library

阅读(1189) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~