有名信号量
① 信号量的使用,求助
信号量:一个整数;
大于或等于0时代表可供并发进程使用的资源实体数;
小于0时代表正在等待使用临界区的进程数;
用于互斥的信号量初始值应大于0;
只能通过P、V原语操作而改变;
信号量元素组成:
1、表示信号量元素的值;
2、最后操作信号量元素的进程ID
3、等待信号量元素值+1的进程数;
4、等待信号量元素值为0的进程数;
二、主要函数
1.1 创建信号量
int semget(
key_t key, //标识信号量的关键字,有三种方法:1、使用IPC——PRIVATE让系统产生,
// 2、挑选一个随机数,3、使用ftok从文件路径名中产生
int nSemes, //信号量集中元素个数
int flag //IPC_CREAT;IPC_EXCL 只有在信号量集不存在时创建
)
成功:返回信号量句柄
失败:返回-1
1.2 使用ftok函数根据文件路径名产生一个关键字
key_t ftok(const char *pathname,int proj_id);
路径名称必须有相应权限
1.3 控制信号量
int semctl(
int semid, //信号量集的句柄
int semnum, //信号量集的元素数
int cmd, //命令
/*union senum arg */... //
)
成功:返回相应的值
失败:返回-1
命令详细说明:
cmd: IPC_RMID 删除一个信号量
IPC_EXCL 只有在信号量集不存在时创建
IPC_SET 设置信号量的许可权
SETVAL 设置指定信号量的元素的值为 agc.val
GETVAL 获得一个指定信号量的值
GETPID 获得最后操纵此元素的最后进程ID
GETNCNT 获得等待元素变为1的进程数
GETZCNT 获得等待元素变为0的进程数
union senum 定义如下:
union senum{
int val;
struct semid_ds *buf;
unsigned short * array;
}agc;
其中 semid_ds 定义如下:
struct semid_ds{
struct ipc_pem sem_pem; //operation pemission struct
time_t sem_otime; //last semop()time
time_t sem_ctime; //last time changed by semctl()
struct sem *sembase; //ptr to first semaphore in array
struct sem_queue *sem_pending; //pending operations
struct sem_queue *sem_pending_last; //last pending operations
struct sem_undo *undo; //undo requests on this arrary
unsigned short int sem_nsems; //number of semaphores in set
};
1.4 对信号量 +1 或 -1 或测试是否为0
int semop(
int semid,
struct sembuf *sops, //指向元素操作数组
unsigned short nsops //数组中元素操作的个数
)
结构 sembuf 定义
sembuf{
short int sem_num; //semaphore number
short int sem_op; //semaphore operaion
short int sem_flg //operation flag
};
三、例子:
2.1 服务器
#include <sys/sem.h>
#include <sys/ipc.h>
#define SEGSIZE 1024
#define READTIME 1
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
//生成信号量
int sem_creat(key_t key)
{
union semun sem;
int semid;
sem.val = 0;
semid = semget(key,1,IPC_CREAT|0666);
if (-1 == semid){
printf("create semaphore error\n");
exit(-1);
}
semctl(semid,0,SETVAL,sem);
return semid;
}
//删除信号量
void del_sem(int semid)
{
union semun sem;
sem.val = 0;
semctl(semid,0,IPC_RMID,sem);
}
//p
int p(int semid)
{
struct sembuf sops={0,+1,IPC_NOWAIT};
return (semop(semid,&sops,1));
}
//v
int v(int semid)
{
struct sembuf sops={0,-1,IPC_NOWAIT};
return (semop(semid,&sops,1));
}
int main()
{
key_t key;
int shmid,semid;
char *shm;
char msg[7] = "-data-";
char i;
struct semid_ds buf;
key = ftok("/",0);
shmid = shmget(key,SEGSIZE,IPC_CREAT|0604);
if (-1 == shmid){
printf(" create shared memory error\n");
return -1;
}
shm = (char *)shmat(shmid,0,0);
if (-1 == (int)shm){
printf(" attach shared memory error\n");
return -1;
}
semid = sem_creat(key);
for (i = 0;i <= 3;i++){
sleep(1);
p(semid);
sleep(READTIME);
msg[5] = '0' + i;
memcpy(shm,msg,sizeof(msg));
sleep(58);
v(semid);
}
shmdt(shm);
shmctl(shmid,IPC_RMID,&buf);
del_sem(semid);
return 0;
//gcc -o shm shm.c -g
}
2.2 客户端
#include <sys/sem.h>
#include <time.h>
#include <sys/ipc.h>
#define SEGSIZE 1024
#define READTIME 1
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
// 打印程序执行时间
void out_time(void)
{
static long start = 0;
time_t tm;
if (0 == start){
tm = time(NULL);
start = (long)tm;
printf(" now start ...\n");
}
printf(" second: %ld \n",(long)(time(NULL)) - start);
}
//创建信号量
int new_sem(key_t key)
{
union semun sem;
int semid;
sem.val = 0;
semid = semget(key,0,0);
if (-1 == semid){
printf("create semaphore error\n");
exit(-1);
}
return semid;
}
//等待信号量变成0
void wait_v(int semid)
{
struct sembuf sops={0,0,0};
semop(semid,&sops,1);
}
int main(void)
{
key_t key;
int shmid,semid;
char *shm;
char msg[100];
char i;
key = ftok("/",0);
shmid = shmget(key,SEGSIZE,0);
if(-1 == shmid){
printf(" create shared memory error\n");
return -1;
}
shm = (char *)shmat(shmid,0,0);
if (-1 == (int)shm){
printf(" attach shared memory error\n");
return -1;
}
semid = new_sem(key);
for (i = 0;i < 3;i ++){
sleep(2);
wait_v(semid);
printf("Message geted is: %s \n",shm + 1);
out_time();
}
shmdt(shm);
return 0;
// gcc -o shmc shmC.c -g
}
② 进程同步的私用信号量
上面我们用wait(消息名)与signal(消息名)的方式,描述了进程同步的一种实现方法。事实上,使用信号量的方法也可实现进程间的同步。 一般来说,我们也可以把各进程之间发送的消息作为信号量看待。与进程互斥时不同的是,这里的信号量只与制约进程及被制约进程有关而不是与整组并发进程有关。因此,我们称该信号量为私用信号量(Private Semaphore)。一个进程Pi的私用信号量Semi是从制约进程发送来的进程Pi的执行条件所需要的消息。与私用信号量相对应,我们称互斥时使用的信号量为公用信号量。
③ linux编程时的信号量问题。 我以前用过的信号量头文件是<semaphore.h>,而现在又发现还有个<sys/sem.h>
semaphore.h 提供的是 POSIX 标准定义的 semaphore 接口 ( sem_open, sem_wait, ...) ,这组接口使用更简单,设计的较好。
而 sys/sem.h 里 提供的是符合 System V 标准的 semaphore接口 (semget, semop, ...),这些接口都比较老了, linux提供主要是为了兼容老代码。
对于 linux 开发来说,新写的代码,都应该考虑采用 POSIX 标准的信号量。
④ 线程同步:何时互斥锁不够,还需要条件变量
信号量强调的是线程(或进程)间的同步:“信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都 在sem_wait的时候,就阻塞在那里)。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
有名信号量:可以用于不同进程间或多线程间的互斥与同步
创建打开有名信号量
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
成功返回信号量指针;失败返回SEM_FAILED,设置errnoname是文件路径名,但不能写成/tmp/a.sem这样的形式,因为在linux下,sem都是在/dev/shm目录下,可写成"/mysem"或"mysem",创建出来的文件都 是"/dev/shm/sem.mysem",mode设置为0666,value设置为信号量的初始值.所需信号灯等已存在条件下指定O_CREAT|O_EXCL却是个错误。
关闭信号量,进程终止时,会自动调用它
int sem_close(sem_t *sem);
成功返回0;失败返回-1,设置errno
删除信号量,立即删除信号量名字,当其他进程都关闭它时,销毁它
int sem_unlink(const char *name);
等待信号量,测试信号量的值,如果其值小于或等于0,那么就等待(阻塞);一旦其值变为大于0就将它减1,并返回
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
成功返回0;失败返回-1,设置errno
当信号量的值为0时,sem_trywait立即返回,设置errno为EAGAIN。如果被某个信号中断,sem_wait会过早地返回,设置errno为EINTR
发出信号量,给它的值加1,然后唤醒正在等待该信号量的进程或线程
int sem_post(sem_t *sem);
成功返回0;失败返回-1,不会改变它的值,设置errno,该函数是异步信号安全的,可以在信号处理程序里调用它无名信号量,用于进程体内各线程间的互斥和同步,使用如下API(无名信号量,基于内存的信号量)
(1)、sem_init
功能:用于创建一个信号量,并初始化信号量的值。
头文件:
函数原型: int sem_init (sem_t* sem, int pshared, unsigned int value);
函数传入值: sem:信号量。pshared:决定信号量能否在几个进程间共享。由于目前LINUX还没有实现进程间共享信息量,所以这个值只能取0。
(2)其他函数。
int sem_wait (sem_t* sem);
int sem_trywait (sem_t* sem);
int sem_post (sem_t* sem);
int sem_getvalue (sem_t* sem);
int sem_destroy (sem_t* sem);
功能:sem_wait和sem_trywait相当于P操作,它们都能将信号量的值减一,两者的区别在于若信号量的值小于零时,sem_wait将会阻塞进程,而sem_trywait则会立即返回。sem_post相当于V操作,它将信号量的值加一,同时发出唤醒的信号给等待的进程(或线程)。
sem_getvalue 得到信号量的值。
sem_destroy 摧毁信号量。
如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。
互斥锁(又名互斥量)强调的是资源的访问互斥:互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
在linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:
对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy.
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
头文件:
返回值: 成功则返回0, 出错则返回错误编号.
说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL. 其他值在以后讲解.
首先说一下加锁函数:
头文件:
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);
返回值: 成功则返回0, 出错则返回错误编号.
条件变量常与互斥锁同时使用,达到线程同步的目的:条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。在发 送信号时,如果没有线程 等待在该条件变量上,那么信号将丢失;而信号量有计数值,每次信号量post操作都会被记录
互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。
2. 互斥锁要么被锁住,要么被解开,和二值信号量类似
3. sem_post是各种同步技巧中,唯一一个能在信号处理程序中安全调用的函数
4. 互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性
5. 互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。
6. 信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。
读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。
读写锁可以由三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写
锁。
在读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。虽然读写锁的实现各不相同,但当读写锁处于读模式锁住状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求。这样可以避免读模式锁长期占用,而等待的写模式锁请求一直得不到满足。
读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥 有这个锁。当读写锁在读状态下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读模式锁的线程读取。
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当他以写模式锁住时,它是以独占模式锁住的。
初始化和销毁:
#include
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
同互斥量以上, 在释放读写锁占用的内存之前, 需要先通过thread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.
读和写:
#include
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
这3个函数分别实现获取读锁, 获取写锁和释放锁的操作. 获取锁的两个函数是阻塞操作, 同样, 非阻塞的函数为:
#include
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.
非阻塞的获取锁操作, 如果可以获取则返回0, 否则返回错误的EBUSY.
虽然读写锁提高了并行性,但是就速度而言并不比互斥量快.
可能这也是即使有读写锁存在还会使用互斥量的原因,因为他在速度方面略胜一筹。这就需要我们在写程序的时候综合考虑速度和并行性并找到一个折中。
比如: 假设使用互斥量需要0.5秒,使用读写锁需要0.8秒。在类似学生管理系统这类软件中,可能百分之九十的时间都是查询操作,那么假如现在突然来个个20个请求,如果使用的是互斥量,那么最后的那个查询请求被满足需要10后。这样,估计没人能受得了。而使用读写锁,应为 读锁能够多次获得。所以所有的20个请求,每个请求都能在1秒左右得到满足。
也就是说,在一些写操作比较多或是本身需要同步的地方并不多的程序中我们应该使用互斥量,而在读操作远大于写操作的一些程序中我们应该使用读写锁来进行同步
条件变量(condition)
条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会察觉到这种改变,因此必须锁定互斥量以后才能计算条件。
条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件
变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
初始化:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.
动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.
#include
int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
成功则返回0, 出错则返回错误编号.
注意:条件变量占用的空间并未被释放。
当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论.
2. 等待条件:
#include
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
成功则返回0, 出错则返回错误编号.
这两个函数分别是阻塞等待和超时等待.
等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样 便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.
当pthread_cond_wait返回时, 互斥量再次被锁住.
pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条 件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条 件变量相关的互斥锁仍将处在锁定状态。
pthread_cond_timedwait函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
注意:pthread_cond_timedwait函数也是退出点。
超时时间参数是指一天中的某个时刻。使用举例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超时返回的错误码是ETIMEDOUT。
3. 通知条件:
#include
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
成功则返回0, 出错则返回错误编号.
这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.
⑤ 线程的几种控制方式以及线程间的几种通信方式
几种进程间的通信方式
(1) 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。
(2)有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
(3)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(4)消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5)信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
(6)共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
(7)套接字(socket):套接口也是一种进程间的通信机制,与其他通信机制不同的是它可以用于不同及其间的进程通信。
几种线程间的通信机制
1、锁机制
1.1 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。
1.2 读写锁:允许多个线程同时读共享数据,而对写操作互斥。
1.3 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
2、信号量机制:包括无名线程信号量与有名线程信号量
3、信号机制:类似于进程间的信号处理。
线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。