线程间同步方式总结 女爷i 2022-07-26 05:18 159阅读 0赞 http://www.cnblogs.com/Creator/archive/2012/04/18/2455584.html http://blog.csdn.net/hongmy525/article/details/5194006 http://blog.csdn.net/qinxiongxu/article/details/7830537 http://www.cnblogs.com/diyingyun/archive/2011/12/04/2275229.html ## [信号量与互斥锁][Link 1] ## 信号量与普通整型变量的区别: ①信号量(semaphore)是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进行访问; ②操作也被成为PV原语(P来源于Dutch proberen"测试",V来源于Dutch verhogen"增加"),而普通整型变量则可以在任何语句块中被访问; 信号量与互斥锁之间的区别: 1. 互斥量用于线程的互斥,信号线用于线程的同步。 这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。 互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。 同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源 2. 互斥量值只能为0/1,信号量值可以为非负整数。 也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。 3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。 信号量 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。 信号量可以分为几类: ² 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。 ² 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。 ² 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。 信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减一。如果计数器大于0,则访问被允许,计数器减1;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。 计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。 Semaphore可以被抽象为五个操作: \- 创建 Create \- 等待 Wait: 线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。 \-释放 Post 执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程。 \-试图等待 TryWait 如果调用TryWait,线程并不真正的去获得信号量,还是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功;否则返回失败。 \-销毁 Destroy 信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。如果关键代码段中没有任何线程,那么线程会立即进入该框图中的那个部分。一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。 动作\\系统 Win32 POSIX 创建 CreateSemaphore sem\_init 等待 WaitForSingleObject sem \_wait 释放 ReleaseMutex sem \_post 试图等待 WaitForSingleObject sem \_trywait 销毁 CloseHandle sem\_destroy 互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯。一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源。 Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex主要的作用是用于互斥。Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待;值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0。 Mutex可以被抽象为四个操作: \- 创建 Create \- 加锁 Lock \- 解锁 Unlock \- 销毁 Destroy Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁(系统一般会在第二次调用立刻返回)。也就是说,加锁和解锁这两个对应的操作,需要在同一个线程中完成。 不同操作系统中提供的Mutex函数: 动作\\系统 Win32 Linyx Solaris 创建 CreateMutex pthread\_mutex\_init mutex\_init 加锁 WaitForSingleObject pthread\_mutex\_lock mutex\_lock 解锁 ReleaseMutex pthread\_mutex\_unlock mutex\_unlock 销毁 CloseHandle pthread\_mutex\_destroy mutex\_destroy # [最全面的linux信号量解析][linux] # 标签: [linux][linux 1][semaphore][][thread][][struct][][system][][null][] 2012-08-04 17:27 33250人阅读 [评论][Link 2](1) 收藏 [举报][Link 3] ![category_icon.jpg][] 分类: 嵌入式linux开发(28) ![arrow_triangle_20_down.jpg][] 2012-06-28 15:08 285人阅读 [评论][Link 4](0) [收藏][linux] [编辑][Link 5] [删除][linux] 信号量 一.什么是信号量 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程) 所拥有。 信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明 它被占用,测试的线程要进入睡眠队列中,等待被唤醒。 二.信号量的分类 在学习信号量之前,我们必须先知道——Linux提供两种信号量: (1) 内核信号量,由内核控制路径使用 (2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量。 POSIX信号量又分为有名信号量和无名信号量。 有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名 信号量,其值保存在内存中。 倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷 失了方向。 三.内核信号量 1.内核信号量的构成 内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而, 当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源 被释放时,进程才再次变为可运行。 只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内 核信号量。 内核信号量是struct semaphore类型的对象,它在<asm/semaphore.h>中定义: struct semaphore \{ atomic\_t count; int sleepers; wait\_queue\_head\_t wait; \} count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这 个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。 wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。 sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。 2.内核信号量中的等待队列(删除,没有联系) 上面已经提到了内核信号量使用了等待队列wait\_queue来实现阻塞操作。 当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。当条件得到满足 时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工 作队列中等待CPU资源,在适当的时机被调度。 内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用 户干涉。由用户真正使用的等待队列我们将在另外的篇章进行详解。 3.内核信号量的相关函数 (1)初始化: void sema\_init (struct semaphore \*sem, int val); void init\_MUTEX (struct semaphore \*sem); //将sem的值置为1,表示资源空闲 void init\_MUTEX\_LOCKED (struct semaphore \*sem); //将sem的值置为0,表示资源忙 (2)申请内核信号量所保护的资源: void down(struct semaphore \* sem); // 可引起睡眠 int down\_interruptible(struct semaphore \* sem); // down\_interruptible能被信号打断 int down\_trylock(struct semaphore \* sem); // 非阻塞函数,不会睡眠。无法锁定资源则 马上返回 (3)释放内核信号量所保护的资源: void up(struct semaphore \* sem); 4.内核信号量的使用例程 在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的 共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux内核中 解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。 ssize\_t globalvar\_write(struct file \*filp, const char \*buf, size\_t len, loff\_t \*off) \{ //获得信号量 if (down\_interruptible(&sem)) \{ return - ERESTARTSYS; \} //将用户空间的数据复制到内核空间的global\_var if (copy\_from\_user(&global\_var, buf, sizeof(int))) \{ up(&sem); return - EFAULT; \} //释放信号量 up(&sem); return sizeof(int); \} 四.POSIX 信号量与SYSTEM V信号量的比较 1. 对POSIX来说,信号量是个非负整数。常用于线程间同步。 而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体, 这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。 2.POSIX信号量的引用头文件是“<semaphore.h>”,而SYSTEM V信号量的引用头文件是 “<sys/sem.h>”。 3.从使用的角度,System V信号量是复杂的,而Posix信号量是简单。比如,POSIX信 号量的创建和初始化或PV操作就很非常方便。 五.POSIX信号量详解 1.无名信号量 无名信号量的创建就像声明一般的变量一样简单,例如:sem\_t sem\_id。然后再初 始化该无名信号量,之后就可以放心使用了。 无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信 号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程 (线程)的共享变量,这两个条件是缺一不可的。 常见的无名信号量相关函数:sem\_destroy int sem\_init(sem\_t \*sem, int pshared, unsigned int value); 1)pshared==0 用于同一多线程的同步; 2)若pshared>0 用于多个相关进程间的同步(即由fork产生的) int sem\_getvalue(sem\_t \*sem, int \*sval); 取回信号量sem的当前值,把该值保存到sval中。 若有1个或更多的线程或进程调用sem\_wait阻塞在该信号量上,该函数返回两种值: 1) 返回0 2) 返回阻塞在该信号量上的进程或线程数目 linux采用返回的第一种策略。 sem\_wait(或sem\_trywait)相当于P操作,即申请资源。 int sem\_wait(sem\_t \*sem); // 这是一个阻塞的函数 测试所指定信号量的值,它的操作是原子的。 若sem>0,那么它减1并立即返回。 若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。 int sem\_trywait(sem\_t \*sem); // 非阻塞的函数 其他的行为和sem\_wait一样,除了: 若sem==0,不是睡眠,而是返回一个错误EAGAIN。 sem\_post相当于V操作,释放资源。 int sem\_post(sem\_t \*sem); 把指定的信号量sem的值加1; 呼醒正在等待该信号量的任意线程。 注意:在这些函数中,只有sem\_post是信号安全的函数,它是可重入函数 (a)无名信号量在多线程间的同步 无名信号量的常见用法是将要保护的变量放在sem\_wait和sem\_post中间所形成的 临界区内,这样该变量就会被保护起来,例如: \#include <pthread.h> \#include <semaphore.h> \#include <sys/types.h> \#include <stdio.h> \#include <unistd.h> int number; // 被保护的全局变量 sem\_t sem\_id; void\* thread\_one\_fun(void \*arg) \{ sem\_wait(&sem\_id); printf("thread\_one have the semaphore\\n"); number++; printf("number = %d\\n",number); sem\_post(&sem\_id); \} void\* thread\_two\_fun(void \*arg) \{ sem\_wait(&sem\_id); printf("thread\_two have the semaphore \\n"); number--; printf("number = %d\\n",number); sem\_post(&sem\_id); \} int main(int argc,char \*argv\[\]) \{ number = 1; pthread\_t id1, id2; sem\_init(&sem\_id, 0, 1); pthread\_create(&id1,NULL,thread\_one\_fun, NULL); pthread\_create(&id2,NULL,thread\_two\_fun, NULL); pthread\_join(id1,NULL); pthread\_join(id2,NULL); printf("main,,,\\n"); return 0; \} 上面的例程,到底哪个线程先申请到信号量资源,这是随机的。如果想要某个特定的顺 序的话,可以用2个信号量来实现。例如下面的例程是线程1先执行完,然后线程2才继 续执行,直至结束。 int number; // 被保护的全局变量 sem\_t sem\_id1, sem\_id2; void\* thread\_one\_fun(void \*arg) \{ sem\_wait(&sem\_id1); printf("thread\_one have the semaphore\\n"); number++; printf("number = %d\\n",number); sem\_post(&sem\_id2); \} void\* thread\_two\_fun(void \*arg) \{ sem\_wait(&sem\_id2); printf("thread\_two have the semaphore \\n"); number--; printf("number = %d\\n",number); sem\_post(&sem\_id1); \} int main(int argc,char \*argv\[\]) \{ number = 1; pthread\_t id1, id2; sem\_init(&sem\_id1, 0, 1); // 空闲的 sem\_init(&sem\_id2, 0, 0); // 忙的 pthread\_create(&id1,NULL,thread\_one\_fun, NULL); pthread\_create(&id2,NULL,thread\_two\_fun, NULL); pthread\_join(id1,NULL); pthread\_join(id2,NULL); printf("main,,,\\n"); return 0; \} (b)无名信号量在相关进程间的同步 说是相关进程,是因为本程序中共有2个进程,其中一个是另外一个的子进程(由 fork 产生)的。 本来对于fork来说,子进程只继承了父进程的代码副本,mutex理应在父子进程 中是相互独立的两个变量,但由于在初始化mutex的时候,由pshared = 1指 定了mutex处于共享内存区域,所以此时mutex变成了父子进程共享的一个变 量。此时,mutex就可以用来同步相关进程了。 \#include <semaphore.h> \#include <stdio.h> \#include <errno.h> \#include <stdlib.h> \#include <unistd.h> \#include <sys/types.h> \#include <sys/stat.h> \#include <fcntl.h> \#include <sys/mman.h> int main(int argc, char \*\*argv) \{ int fd, i,count=0,nloop=10,zero=0,\*ptr; sem\_t mutex; //open a file and map it into memory fd = open("log.txt",O\_RDWR|O\_CREAT,S\_IRWXU); write(fd,&zero,sizeof(int)); ptr = mmap( NULL,sizeof(int),PROT\_READ | PROT\_WRITE,MAP\_SHARED,fd,0 ); close(fd); /\* create, initialize semaphore \*/ if( sem\_init(&mutex,1,1) < 0) // \{ perror("semaphore initilization"); exit(0); \} if (fork() == 0) \{ /\* child process\*/ for (i = 0; i < nloop; i++) \{ sem\_wait(&mutex); printf("child: %d\\n", (\*ptr)++); sem\_post(&mutex); \} exit(0); \} /\* back to parent process \*/ for (i = 0; i < nloop; i++) \{ sem\_wait(&mutex); printf("parent: %d\\n", (\*ptr)++); sem\_post(&mutex); \} exit(0); \} 2.有名信号量 有名信号量的特点是把信号量的值保存在文件中。 这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关 进程。 (a)有名信号量能在进程间共享的原因 由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父 进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当 然文件里面保存的有名信号量值就共享了。 (b)有名信号量相关函数说明 有名信号量在使用的时候,和无名信号量共享sem\_wait和sem\_post函数。 区别是有名信号量使用sem\_open代替sem\_init,另外在结束的时候要像关闭文件 一样去关闭这个有名信号量。 (1)打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完 成了信号量的创建、初始化和权限的设置。 sem\_t \*sem\_open(const char \*name, int oflag, mode\_t mode , int value); name是文件的路径名; Oflag 有O\_CREAT或O\_CREAT|EXCL两个取值; mode\_t控制新的信号量的访问权限; Value指定信号量的初始化值。 注意: 这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建 在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都 是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。 当oflag = O\_CREAT时,若name指定的信号量不存在时,则会创建一个,而且后 面的mode和value参数必须有效。若name指定的信号量已存在,则直接打开该信号量, 同时忽略mode和value参数。 当oflag = O\_CREAT|O\_EXCL时,若name指定的信号量已存在,该函数会直接返 回error。 (2) 一旦你使用了一信号量,销毁它们就变得很重要。 在做这个之前,要确定所有对这个有名信号量的引用都已经通过sem\_close()函数 关闭了,然后只需在退出或是退出处理函数中调用sem\_unlink()去删除系统中的信号量, 注意如果有任何的处理器或是线程引用这个信号量,sem\_unlink()函数不会起到任何的作 用。 也就是说,必须是最后一个使用该信号量的进程来执行sem\_unlick才有效。因为每个 信号灯有一个引用计数器记录当前的打开次数,sem\_unlink必须等待这个数为0时才能把 name所指的信号灯从文件系统中删除。也就是要等待最后一个sem\_close发生。 (c)有名信号量在无相关进程间的同步 前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于 共享内存区,只有这样才能被无相关的进程所共享。 在下面这个例子中,服务进程和客户进程都使用shmget和shmat来获取得一块共享内 存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。 <u>File1: server.c </u> \#include <sys/types.h> \#include <sys/ipc.h> \#include <sys/shm.h> \#include <stdio.h> \#include <semaphore.h> \#include <sys/types.h> \#include <sys/stat.h> \#include <fcntl.h> \#define SHMSZ 27 char SEM\_NAME\[\]= "vik"; int main() \{ char ch; int shmid; key\_t key; char \*shm,\*s; sem\_t \*mutex; //name the shared memory segment key = 1000; //create & initialize semaphore mutex = sem\_open(SEM\_NAME,O\_CREAT,0644,1); if(mutex == SEM\_FAILED) \{ perror("unable to create semaphore"); sem\_unlink(SEM\_NAME); exit(-1); \} //create the shared memory segment with this key shmid = shmget(key,SHMSZ,IPC\_CREAT|0666); if(shmid<0) \{ perror("failure in shmget"); exit(-1); \} //attach this segment to virtual memory shm = shmat(shmid,NULL,0); //start writing into memory s = shm; for(ch='A';ch<='Z';ch++) \{ sem\_wait(mutex); \*s++ = ch; sem\_post(mutex); \} //the below loop could be replaced by binary semaphore while(\*shm != '\*') \{ sleep(1); \} sem\_close(mutex); sem\_unlink(SEM\_NAME); shmctl(shmid, IPC\_RMID, 0); exit(0); \} <u>File 2: client.c</u> \#include <sys/types.h> \#include <sys/ipc.h> \#include <sys/shm.h> \#include <stdio.h> \#include <semaphore.h> \#include <sys/types.h> \#include <sys/stat.h> \#include <fcntl.h> \#define SHMSZ 27 char SEM\_NAME\[\]= "vik"; int main() \{ char ch; int shmid; key\_t key; char \*shm,\*s; sem\_t \*mutex; //name the shared memory segment key = 1000; //create & initialize existing semaphore mutex = sem\_open(SEM\_NAME,0,0644,0); if(mutex == SEM\_FAILED) \{ perror("reader:unable to execute semaphore"); sem\_close(mutex); exit(-1); \} //create the shared memory segment with this key shmid = shmget(key,SHMSZ,0666); if(shmid<0) \{ perror("reader:failure in shmget"); exit(-1); \} //attach this segment to virtual memory shm = shmat(shmid,NULL,0); //start reading s = shm; for(s=shm;\*s!=NULL;s++) \{ sem\_wait(mutex); putchar(\*s); sem\_post(mutex); \} //once done signal exiting of reader:This can be replaced by another semaphore \*shm = '\*'; sem\_close(mutex); shmctl(shmid, IPC\_RMID, 0); exit(0); \} 六.SYSTEM V信号量 这是信号量值的集合,而不是单个信号量。相关的信号量操作函数由<sys/ipc.h>引用。 1.信号量结构体 内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义: struct semid\_ds \{ struct ipc\_perm sem\_perm; /\* 信号量集的操作许可权限 \*/ struct sem \*sem\_base; /\* 某个信号量sem结构数组的指针,当前信号量集 中的每个信号量对应其中一个数组元素 \*/ ushort sem\_nsems; /\* sem\_base 数组的个数 \*/ time\_t sem\_otime; /\* 最后一次成功修改信号量数组的时间 \*/ time\_t sem\_ctime; /\* 成功创建时间 \*/ \}; struct sem \{ ushort semval; /\* 信号量的当前值 \*/ short sempid; /\* 最后一次返回该信号量的进程ID 号 \*/ ushort semncnt; /\* 等待semval大于当前值的进程个数 \*/ ushort semzcnt; /\* 等待semval变成0的进程个数 \*/ \}; 2.常见的SYSTEM V信号量函数 (a)关键字和描述符 SYSTEM V信号量是SYSTEM V IPC(即SYSTEM V进程间通信)的组成部分,其他 的有SYSTEM V消息队列,SYSTEM V共享内存。而关键字和IPC描述符无疑是它们的共 同点,也使用它们,就不得不先对它们进行熟悉。这里只对SYSTEM V信号量进行讨论。 IPC描述符相当于引用ID号,要想使用SYSTEM V信号量(或MSG、SHM),就必须 用IPC描述符来调用信号量。而IPC描述符是内核动态提供的(通过semget来获取),用 户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字KEY来 定位描述符。 某个KEY只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和 客户事先认可共同使用某个KEY,那么大家就都能定位到同一个描述符,也就能定位到同 一个信号量,这样就达到了SYSTEM V信号量在进程间共享的目的。 (b)创建和打开信号量 int semget(key\_t key, int nsems, int oflag) (1) nsems>0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。 (2) nsems==0 : 访问一个已存在的集合 (3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。 (4) 创建成功后信号量结构被设置: .sem\_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID .oflag 参数中的读写权限位存入sem\_perm.mode .sem\_otime 被置为0,sem\_ctime被设置为当前时间 .sem\_nsems 被置为nsems参数的值 该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET\_VAL,SETALL 初始化的。 semget函数执行成功后,就产生了一个由内核维持的类型为semid\_ds结构体的信号量 集,返回semid就是指向该信号量集的引索。 (c)关键字的获取 有多种方法使客户机和服务器在同一IPC结构上会合: (1) 服务器可以指定关键字IPC\_PRIVATE创建一个新IPC结构,将返回的标识符存放在某 处(例如一个文件)以便客户机取用。关键字 IPC\_PRIVATE保证服务器创建一个新IPC结 构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件 取得此标识符。 IPC\_PRIVATE关键字也可用于父、子关系进程。父进程指定 IPC\_PRIVATE创建一个新 IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为exec函数 的一个参数传给一个新程序。 (2) 在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键 字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个 IPC结构相结合,在 此情况下,get函数(msgget、semget或shmget)出错返回。服务器必须处理这一错误,删除 已存在的IPC结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。 (3) 客户机和服务器认同一个路径名和课题I D(课题I D是0 ~ 2 5 5之间的字符值) ,然 后调用函数ftok将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键字的 问题。 使用ftok并非高枕无忧。有这样一种例外:服务器使用ftok获取得一个关键字后,该文 件就被删除了,然后重建。此时客户端以此重建后的文件来ftok所获取的关键字就和服务器 的关键字不一样了。所以一般商用的软件都不怎么用ftok。 一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使 用ftok,而只是在该头文件中存放一个大家都知道的关键字。 (d)设置信号量的值(PV操作) int semop(int semid, struct sembuf \*opsptr, size\_t nops); (1) semid: 是semget返回的semid (2)opsptr: 指向信号量操作结构数组 (3) nops : opsptr所指向的数组中的sembuf结构体的个数 struct sembuf \{ short sem\_num; // 要操作的信号量在信号量集里的编号, short sem\_op; // 信号量操作 short sem\_flg; // 操作表示符 \}; (4) 若sem\_op 是正数,其值就加到semval上,即释放信号量控制的资源 若sem\_op 是0,那么调用者希望等到semval变为0,如果semval是0就返回; 若sem\_op 是负数,那么调用者希望等待semval变为大于或等于sem\_op的绝对值 例如,当前semval为2,而sem\_op = -3,那么怎么办? 注意:semval是指semid\_ds中的信号量集中的某个信号量的值 (5) sem\_flg SEM\_UNDO 由进程自动释放信号量 IPC\_NOWAIT 不阻塞 到这里,读者肯定有个疑惑:semop希望改变的semval到底在哪里?我们怎么没看到 有它的痕迹?其实,前面已经说明了,当使用semget时,就产生了一个由内核维护的信号 量集(当然每个信号量值即semval也是只由内核才能看得到了),用户能看到的就是返回 的semid。内核通过semop 函数的参数,知道应该去改变semid 所指向的信号量的哪个 semval。 (e)对信号集实行控制操作(semval的赋值等) int semctl(int semid, int semum, int cmd, ../\* union semun arg \*/); semid是信号量集合; semnum是信号在集合中的序号; semum是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成: union semun \{ int val; // cmd == SETVAL struct semid\_ds \*buf // cmd == IPC\_SET或者 cmd == IPC\_STAT ushort \*array; // cmd == SETALL,或 cmd = GETALL \}; val只有cmd ==SETVAL时才有用,此时指定的semval = arg.val。 注意:当cmd == GETVAL时,semctl函数返回的值就是我们想要的semval。千万不要 以为指定的semval被返回到arg.val中。 array指向一个数组,当cmd==SETALL时,就根据arg.array来将信号量集的所有值都 赋值;当cmd ==GETALL时,就将信号量集的所有值返回到arg.array指定的数组中。 buf 指针只在cmd==IPC\_STAT 或IPC\_SET 时有用,作用是semid 所指向的信号量集 (semid\_ds机构体)。一般情况下不常用,这里不做谈论。 另外,cmd == IPC\_RMID还是比较有用的。 (f)例码 \#include <sys/types.h> \#include <sys/ipc.h> \#include <sys/sem.h> \#include <stdio.h> static int nsems; static int semflg; static int semid; int errno=0; union semun \{ int val; struct semid\_ds \*buf; unsigned short \*array; \}arg; int main() \{ struct sembuf sops\[2\]; //要用到两个信号量,所以要定义两个操作数组 int rslt; unsigned short argarray\[80\]; arg.array = argarray; semid = semget(IPC\_PRIVATE, 2, 0666); if(semid < 0 ) \{ printf("semget failed. errno: %d\\n", errno); exit(0); \} //获取0th信号量的原始值 rslt = semctl(semid, 0, GETVAL); printf("val = %d\\n",rslt); //初始化0th信号量,然后再读取,检查初始化有没有成功 arg.val = 1; // 同一时间只允许一个占有者 semctl(semid, 0, SETVAL, arg); rslt = semctl(semid, 0, GETVAL); printf("val = %d\\n",rslt); sops\[0\].sem\_num = 0; sops\[0\].sem\_op = -1; sops\[0\].sem\_flg = 0; sops\[1\].sem\_num = 1; sops\[1\].sem\_op = 1; sops\[1\].sem\_flg = 0; rslt=semop(semid, sops, 1); //申请0th信号量,尝试锁定 if (rslt < 0 ) \{ printf("semop failed. errno: %d\\n", errno); exit(0); \} //可以在这里对资源进行锁定 sops\[0\].sem\_op = 1; semop(semid, sops, 1); //释放0th信号量 rslt = semctl(semid, 0, GETVAL); printf("val = %d\\n",rslt); rslt=semctl(semid, 0, GETALL, arg); if (rslt < 0) \{ printf("semctl failed. errno: %d\\n", errno); exit(0); \} printf("val1:%d val2: %d\\n",(unsigned int)argarray\[0\],(unsigned int)argarray\[1\]); if(semctl(semid, 1, IPC\_RMID) == -1) \{ Perror(“semctl failure while clearing reason”); \} return(0); \} 七.信号量的牛刀小试——生产者与消费者问题 1.问题描述: 有一个长度为N的缓冲池为生产者和消费者所共有,只要缓冲池未满,生产者便可将 消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。生产者往缓冲池 放信息的时候,消费者不可操作缓冲池,反之亦然。 2.使用多线程和信号量解决该经典问题的互斥 \#include <pthread.h> \#include <stdio.h> \#include <semaphore.h> \#define BUFF\_SIZE 10 char buffer\[BUFF\_SIZE\]; char count; // 缓冲池里的信息数目 sem\_t sem\_mutex; // 生产者和消费者的互斥锁 sem\_t p\_sem\_mutex; // 空的时候,对消费者不可进 sem\_t c\_sem\_mutex; // 满的时候,对生产者不可进 void \* Producer() \{ while(1) \{ sem\_wait(&p\_sem\_mutex); //当缓冲池未满时 sem\_wait(&sem\_mutex); //等待缓冲池空闲 count++; sem\_post(&sem\_mutex); if(count < BUFF\_SIZE)//缓冲池未满 sem\_post(&p\_sem\_mutex); if(count > 0) //缓冲池不为空 sem\_post(&c\_sem\_mutex); \} \} void \* Consumer() \{ while(1) \{ sem\_wait(&c\_sem\_mutex);//缓冲池未空时 sem\_wait(&sem\_mutex); //等待缓冲池空闲 count--; sem\_post(&sem\_mutex); if(count > 0) sem\_post(c\_sem\_nutex); \} \} int main() \{ pthread\_t ptid,ctid; //initialize the semaphores sem\_init(&empty\_sem\_mutex,0,1); sem\_init(&full\_sem\_mutex,0,0); //creating producer and consumer threads if(pthread\_create(&ptid, NULL,Producer, NULL)) \{ printf("\\n ERROR creating thread 1"); exit(1); \} if(pthread\_create(&ctid, NULL,Consumer, NULL)) \{ printf("\\n ERROR creating thread 2"); exit(1); \} if(pthread\_join(ptid, NULL)) /\* wait for the producer to finish \*/ \{ printf("\\n ERROR joining thread"); exit(1); \} if(pthread\_join(ctid, NULL)) /\* wait for consumer to finish \*/ \{ printf("\\n ERROR joining thread"); exit(1); \} sem\_destroy(&empty\_sem\_mutex); sem\_destroy(&full\_sem\_mutex); //exit the main thread pthread\_exit(NULL); return 1; \} # [多线程同步条件变量][Link 6] # 最近看《UNIX环境高级编程》多线程同步,看到他举例说条件变量pthread\_cond\_t怎么用,愣是没有看懂,只好在网上找了份代码,跑了跑,才弄明白 **\[cpp\]** [view plain][] [copy][view plain] 1. \#include <pthread.h> 2. \#include <stdio.h> 3. \#include <stdlib.h> 4. pthread\_mutex\_t mutex = PTHREAD\_MUTEX\_INITIALIZER;/\*初始化互斥锁\*/ 5. pthread\_cond\_t cond = PTHREAD\_COND\_INITIALIZER;/\*初始化条件变量\*/ 6. **void** \*thread1(**void** \*); 7. **void** \*thread2(**void** \*); 8. **int** i=1; 9. **int** main(**void**) 10. \{ 11. pthread\_t t\_a; 12. pthread\_t t\_b; 13. pthread\_create(&t\_a,NULL,thread1,(**void** \*)NULL);/\*创建进程t\_a\*/ 14. pthread\_create(&t\_b,NULL,thread2,(**void** \*)NULL); /\*创建进程t\_b\*/ 15. pthread\_join(t\_a, NULL);/\*等待进程t\_a结束\*/ 16. pthread\_join(t\_b, NULL);/\*等待进程t\_b结束\*/ 17. pthread\_mutex\_destroy(&mutex); 18. pthread\_cond\_destroy(&cond); 19. exit(0); 20. \} 21. **void** \*thread1(**void** \*junk) 22. \{ 23. **for**(i=1;i<=6;i++) 24. \{ 25. pthread\_mutex\_lock(&mutex);/\*锁住互斥量\*/ 26. printf("thread1: lock %d/n", \_\_LINE\_\_); 27. **if**(i%3==0)\{ 28. printf("thread1:signal 1 %d/n", \_\_LINE\_\_); 29. pthread\_cond\_signal(&cond);/\*条件改变,发送信号,通知t\_b进程\*/ 30. printf("thread1:signal 2 %d/n", \_\_LINE\_\_); 31. sleep(1); 32. \} 33. pthread\_mutex\_unlock(&mutex);/\*解锁互斥量\*/ 34. printf("thread1: unlock %d/n/n", \_\_LINE\_\_); 35. sleep(1); 36. \} 37. \} 38. **void** \*thread2(**void** \*junk) 39. \{ 40. **while**(i<6) 41. \{ 42. pthread\_mutex\_lock(&mutex); 43. printf("thread2: lock %d/n", \_\_LINE\_\_); 44. **if**(i%3!=0)\{ 45. printf("thread2: wait 1 %d/n", \_\_LINE\_\_); 46. pthread\_cond\_wait(&cond,&mutex);/\*解锁mutex,并等待cond改变\*/ 47. printf("thread2: wait 2 %d/n", \_\_LINE\_\_); 48. \} 49. pthread\_mutex\_unlock(&mutex); 50. printf("thread2: unlock %d/n/n", \_\_LINE\_\_); 51. sleep(1); 52. \} 53. \} 编译: \[X61@horizon threads\]$ gcc thread\_cond.c -lpthread -o tcd 以下是程序运行结果: \[X61@horizon threads\]$ ./tcd thread1: lock 30 thread1: unlock 40 thread2: lock 52 thread2: wait 1 55 thread1: lock 30 thread1: unlock 40 thread1: lock 30 thread1:signal 1 33 thread1:signal 2 35 thread1: unlock 40 thread2: wait 2 57 thread2: unlock 61 thread1: lock 30 thread1: unlock 40 thread2: lock 52 thread2: wait 1 55 thread1: lock 30 thread1: unlock 40 thread1: lock 30 thread1:signal 1 33 thread1:signal 2 35 thread1: unlock 40 thread2: wait 2 57 thread2: unlock 61 这里的两个关键函数就在pthread\_cond\_wait和pthread\_cond\_signal函数。 本例中: 线程一先执行,获得mutex锁,打印,然后释放mutex锁,然后阻塞自己1秒。 **线程二此时和线程一应该是并发的执行** ,这里是一个要点,为什么说是线程此时是并发的执行,因为此时不做任何干涉的话,是没有办法确定是线程一先获得执行还是线程二先获得执行,到底那个线程先获得执行,取决于操作系统的调度,想刻意的让线程2先执行,可以让线程2一出来,先sleep一秒。 这里并发执行的情况是,线程一先进入循环,然后获得锁,此时估计线程二执行,阻塞在 pthread\_mutex\_lock(&mutex); 这行语句中,直到线程1释放mutex锁 pthread\_mutex\_unlock(&mutex);/\*解锁互斥量\*/ 然后线程二得已执行,获取metux锁,满足if条件,到**pthread\_cond\_wait** (&cond,&mutex);/\*等待\*/ 这里的线程二阻塞,不仅仅是等待cond变量发生改变,**同时释放mutex锁** ,因为当时看书没有注意,所以这里卡了很久。 mutex锁释放后,线程1终于获得了mutex锁,得已继续运行,当线程1的if(i%3==0)的条件满足后,通过pthread\_cond\_signal发送信号,告诉等待cond的变量的线程(这个情景中是线程二),cond条件变量已经发生了改变。 **不过此时线程二并没有立即得到运行** ,因为线程二还在等待mutex锁的释放,所以线程一继续往下走,直到线程一释放mutex锁,线程二才能停止等待,打印语句,然后往下走通过pthread\_mutex\_unlock(&mutex)释放mutex锁,进入下一个循环。 # [线程同步之条件变量使用手记][Link 7] # 由来: 最近一直在想怎么高效率的在IO线程接收到数据时通知逻辑线程(基于线程池)工作的问题,像网络编程的服务器模型的一些模型都需要用到这个实现,下面我这里简单的罗列一个多线程的网络服务器模型 半同步/半异步(half-sync/half-async): 许多餐厅使用 半同步/半异步 模式的变体。例如,餐厅常常雇佣一个领班负责迎接顾客,并在餐厅繁忙时留意给顾客安排桌位,为等待就餐的顾客按序排队是必要的。领班由所有顾客“共享”,不能被任何特定顾客占用太多时间。当顾客在一张桌子入坐后,有一个侍应生专门为这张桌子服务。 对于上面罗列的这种模型,本文讨论的问题是当领班接到客人时,如何高效率的通知侍应生去服务顾客. 在我们使用很广泛的线程池实现中,也会有一样的问题 方法实现: 1.使用锁+轮询 使用这种方法可以很简单的实现,但是会有一定的性能消耗,其还有一个点要好好把握,就是一次轮询没有结果后相隔多久进行下一次的轮询,间隔时间太短,消耗的CPU资源较多,间隔时间太长,不能很及时的响应请求。这就相当于上面的这个例子,侍应生时不时的取询问领班有没有顾客到来 2.使用条件变量的线程同步 线程条件变量pthread\_cond\_t 线程等待某个条件 int pthread\_cond\_timedwait(pthread\_cond\_t \*restrict cond,pthread\_mutex\_t \*restrict mutex,const struct timespec \*restrictabstime); int pthread\_cond\_wait(pthread\_cond\_t \*restrict cond,pthread\_mutex\_t \*restrict mutex); 通知函数 通知所有的线程 int pthread\_cond\_broadcast(pthread\_cond\_t \*cond); 只通知一个线程 int pthread\_cond\_signal(pthread\_cond\_t \*cond); 正确的使用方法 pthread\_cond\_wait用法: pthread\_mutex\_lock(&mutex); while(condition\_is\_false) \{ pthread\_cond\_wait(&cond,&mutex); \} condition\_is\_false=true; //此操作是带锁的,也就是说只有一个线程同时进入这块 pthread\_mutex\_unlock(&mutex); pthread\_cond\_signal用法: pthread\_mutex\_lock(&mutex); condition\_is\_false=false; pthread\_cond\_signal(&cond) pthread\_mutex\_unlock(&mutex) 我刚初用的时候,觉得非常的奇怪,为什么要这样用,加了mutex后还需要一个condition\_is\_false变量来表示有没有活干。其实这样子的一个操作主要是为了解决“假激活”问题,因为我么您这里的使用场景,只需要激活一个线程,因为一个线程干一个活,而不是多个线程干一个活,所以为了避免线程被激活了,但实际又没有事情干,所以使用了这么一套机制。 实际上,信号和pthread\_cond\_broadcast是两个常见的导致假唤醒的情况。假如条件变量上有多个线程在等待,pthread\_cond\_broadcast会唤醒所有的等待线程,而pthread\_cond\_signal只会唤醒其中一个等待线程。这样,pthread\_cond\_broadcast的情况也许要在pthread\_cond\_wait前使用while循环来检查条件变量。 来个例子: ![ContractedBlock.gif][] 事实上上面的例子无论是使用pthread\_cond\_signal还是pthread\_cond\_broadcast,都只会打印Thread awake, finish work5次,大家可能会觉得非常奇怪,但是实际情况就是这样的。 为了明白其pthread\_cont\_wait内部干了什么工作,有必要深入一下其内部实现。 关于其内部实现伪代码如下: ![复制代码][copycode.gif] 1 pthread_cond_wait(mutex, cond): 2 value = cond->value; /* 1 */ 3 pthread_mutex_unlock(mutex); /* 2 */ 4 pthread_mutex_lock(cond->mutex); /* 10 */ pthread_cond_t自带一个mutex来互斥对waiter等待链表的操作 5 if (value == cond->value) { /* 11 */ 检查一次是不是cond有被其他线程设置过,相当于单例模式的第二次检测是否为NULL 6 me->next_cond = cond->waiter; 7 cond->waiter = me;//链表操作 8 pthread_mutex_unlock(cond->mutex); 9 unable_to_run(me); 10 } else 11 pthread_mutex_unlock(cond->mutex); /* 12 */ 12 pthread_mutex_lock(mutex); /* 13 */ 13 14 pthread_cond_signal(cond): 15 pthread_mutex_lock(cond->mutex); /* 3 */ 16 cond->value++; /* 4 */ 17 if (cond->waiter) { /* 5 */ 18 sleeper = cond->waiter; /* 6 */ 19 cond->waiter = sleeper->next_cond; /* 7 */ //链表操作 20 able_to_run(sleeper); /* 8 */ 运行sleep的线程,即上面的me 21 } 22 pthread_mutex_unlock(cond->mutex); /* 9 */ ![复制代码][copycode.gif] pthread\_cond\_broadcast虽然能够激活所有的线程,但是激活之后会有mutex锁,也就是说他的激活是顺序进行的,只有第一个激活的线程调用pthread\_mutex\_unlock(&mutex)后,后一个等待的线程才会继续运行.因为从pthread\_cond\_wait(&cond,&mutex)到pthread\_mutex\_unlock(&mutex)区间是加的独占锁,从wait激活后的第一个线程占用了这个锁,所以其他的线程不能运行,只能等待。所以当第一个被激活的线程修改了condition\_is\_false后(上面测试代码的workToDo),接着调用pthread\_mutex\_unlock(&mutex)后,此时其他等待在cond的一个线程会激活,但是此时condition\_is\_false已经被设置,所以他跑不出while循环,当调用pthread\_cond\_wait时,其内部pthread\_mutex\_unlock(mutex)调用会导致另一个在它后面的等待在cond的线程被激活。 所以,通过这种方式,即便是误调用了pthread\_cond\_broadcast或者由于信号中断的原因激活了所有在等待条件的线程,也能保证其结果是正确的。 另外说一句题外话,很多人写的基于条件变量线程同步的框架,说自己是无锁的,其实这是不对的,只是内部锁的机制在pthread\_cond\_wait实现了而已,其还是基于互斥锁的实现。真正想要达到无锁的可以关注一下lockfree相关的CAS算法,其内部使用一个intel CPU的cmpxchg8指令完成的,其实这种实现个人认为和传统锁相比只是一个非阻塞锁和阻塞锁的区别。 [Link 1]: http://www.cnblogs.com/diyingyun/archive/2011/12/04/2275229.html [linux]: http://blog.csdn.net/qinxiongxu/article/details/7830537 [linux 1]: http://www.csdn.net/tag/linux [semaphore]: http://www.csdn.net/tag/semaphore [thread]: http://www.csdn.net/tag/thread [struct]: http://www.csdn.net/tag/struct [system]: http://www.csdn.net/tag/system [null]: http://www.csdn.net/tag/null [Link 2]: http://blog.csdn.net/qinxiongxu/article/details/7830537#comments [Link 3]: http://blog.csdn.net/qinxiongxu/article/details/7830537#report [category_icon.jpg]: /images/20220724/887dcd50e6994ab6894b34f0d132c853.png [arrow_triangle_20_down.jpg]: /images/20220724/0e4045d547a843d786d03f169783d5af.png [Link 4]: http://blog.csdn.net/qinxiongxu/article/details/7699465#comments [Link 5]: http://write.blog.csdn.net/postedit/7699465 [Link 6]: http://blog.csdn.net/hongmy525/article/details/5194006 [view plain]: http://blog.csdn.net/hongmy525/article/details/5194006# [Link 7]: http://www.cnblogs.com/Creator/archive/2012/04/18/2455584.html [ContractedBlock.gif]: /images/20220724/3a5077db5441473590f70d1bf3b9316e.png [copycode.gif]: /images/20220724/d2a26ce8b7d241208752c9ece3b75a12.png
还没有评论,来说两句吧...