Posix信号量
Posix 信号量 | |
有名信号量 | 无名信号量 |
sem_open | sem_init |
sem_close | sem_destroy |
sem_unlink |
|
sem_wait | |
sem_post |
有名信号量
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t * sem_open(const char *name, int oflag);
sem_t * sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
与Posix类IPC用法类似: 名字以/somename形式标识,且只能有一个/ ,并且总长不能超过NAME_MAX-4 (i.e., 251)。
Posix有名信号量需要用sem_open 函数创建或打开,PV操作分别是sem_wait 和 sem_post,可以使用sem_close 关闭,删除用sem_unlink。
有名信号量用于不需要共享内存的进程间同步(可以通过名字访问), 类似System V 信号量。
匿名信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
匿名信号量只存在于内存中, 并要求使用信号量的进程必须可以访问内存; 这意味着他们只能应用在同一进程中的线程, 或者不同进程中已
经映射相同内存内容到它们的地址空间中的线程.
匿名信号量必须用sem_init 初始化,sem_init 函数的第二个参数pshared决定了线程共享(pshared=0)还是进程共享(pshared!=0),也
可以用sem_post 和sem_wait 进行操作,在共享内存释放前,匿名信号量要先用sem_destroy 销毁。
Posix信号量PV操作
int sem_wait(sem_t *sem); //P操作
int sem_post(sem_t *sem); //V操作
wait操作实现对信号量的减1, 如果信号量计数原先为0则会发生阻塞;
post操作将信号量加1, 在调用sem_post时, 如果在调用sem_wait中发生了进程阻塞, 那么进程会被唤醒并且sem_post增1的信号量计数会再次被sem_wait减1;
Posix互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *mutexattr); //互斥锁初始化, 注意:函数成功执行后,互斥锁被初始化为未锁住状态。
int pthread_mutex_lock(pthread_mutex_t *mutex); //互斥锁上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); //互斥锁判断上锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //互斥锁解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex); //消除互斥锁
互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,也就是上锁/解锁,可以把互斥锁看作某种意
义上的全局变量。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个
已经被上锁的互斥锁,则该线程就会阻塞,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁保证让每个线程对共享资源按顺序进
行原子操作。
其中,互斥锁可以分为快速互斥锁(默认互斥锁)、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得
到互斥锁时是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够成功地返回,并且增加调用
线程在互斥上加锁的次数,而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。
生产者消费者问题
有一个缓冲区(有界的缓冲区,也就是说:一定的容量),对于生产者而言:要生产产品的时候要来判定,当前这个
仓库,这个缓冲区是不是满了,
如果满了,那么生产者将阻塞 p(sem_full),每当我们生产一个产品的时候,这个信号量的计数值就减1
如果仓库还没有满的话,,就意味着我们可以生产产品,一旦生产一个产品,也就说明仓库不是空的状态了
v(sem_empty),此刻就是告诉消费者,现在可以消费了
另外在生产产品的时候,生产者可以有多个,那么一定会对缓冲区进行操作。那么缓冲区也就需要互斥来访问。
所以这里,我们还用到了p(mutex),v(mutex)
如上:就是生产者要做的事情了
生产者可以有多个,那么消费者也可以有多个了
那么消费者要完成的任务就是:
判定一下当前缓冲区(仓库)是不是为空的(p(sem_empty))
如果是空的话,那么它将不能消费产品,它需要等待生产者给消费者一个信号(也就是刚才的v(sem_empty),使得信
号量加1,意味着这边有产品了)
那么有产品的话,我就可以消费产品了
一旦消费了一个产品的话,那么就会使得仓库的容量加1(v(sem_full))
同样的,我们在消费产品的时候,也要互斥的访问 p(mutex),v(mutex)
那么刚开始的时候,我们是可以生产产品的,但是不能消费产品,所以说:sem_full的初始值为仓库的容量(假设为
10)
sem_full(10)
sem_empty(0)
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
#define CONSUMERS_COUNT 1
#define PRODUCERS_COUNT 5
//两个生产者一个消费者
#define BUFFSIZE 10
//缓冲区的容量10
int g_buffer[BUFFSIZE];
unsigned short in=0;
unsigned short out = 0;
//生产的时候从一个位置放,取的时候从一个位置取
unsigned short produce_id = 0; //当前正在生产产品的id
unsigned short consume_id = 0; //当前正在消费的id也从0开始
//另外这个缓冲区也实现为环形缓冲区
//另外我们还需要定义几个同步对象,两个信号量,一个互斥锁
sem_t g_sem_full;
sem_t g_sem_empty;
pthread_mutex_t g_mutex;
//另外我们还要注意到建立线程(消费者+生产者的个数)
pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT];
void *consume(void *arg)
{
int i;
int num = (int)arg;
while(1)
{
printf("%d wait buffer not empty\n", num);
sem_wait(&g_sem_empty);
pthread_mutex_lock(&g_mutex);
for(i = 0; i<BUFFSIZE; i++)
{
printf("%02d ", i);
if(g_buffer[i] == -1)
printf("%s", "null");
else
printf("%d", g_buffer[i]);
if(i == out)
printf("\t<---consume");
printf("\n");
}
consume_id = g_buffer[out];
printf("%d begin consume product %d\n",num, consume_id);
g_buffer[out] = -1;
out = (out+1)%BUFFSIZE;
printf("%d end consume product %d\n",num, consume_id);
pthread_mutex_unlock(&g_mutex);
sem_post(&g_sem_full);
sleep(1);
}
return NULL;
}
void *produce(void *arg)
{
int num = (int)arg;
int i;
//不管是生产者还是消费者,都应该在不停的生产消费
while(1)
{
printf("%d wait buffer not full\n", num);
sem_wait(&g_sem_full);
//等待一个满的信号量,如果不满我们就可以生产了
pthread_mutex_lock(&g_mutex);
//加一个锁的机制
//接下来就是生产产品
//先打印仓库当前的状态
for(i = 0; i<BUFFSIZE; i++)
{
printf("%02d ", i);
if(g_buffer[i] == -1)
printf("%s", "null");
else
printf("%d", g_buffer[i]);
if(i == in) //那就是我们生产产品的地方
{
printf("\t<---produce");
}
printf("\n");
}
//生产产品
printf("%d begin produce product %d\n",num, produce_id);
g_buffer[in] = produce_id;
in = (in + 1)%BUFFSIZE;
printf("%d end produce product %d\n",num, produce_id++);
pthread_mutex_unlock(&g_mutex);
//生产了一个产品,v一个空的信号量
sem_post(&g_sem_empty);
sleep(5);
}
return NULL;
}
int main(void)
{
int i;
for(i = 0; i< BUFFSIZE; i++)
g_buffer[i] = -1;
sem_init(&g_sem_full, 0, BUFFSIZE);
sem_init(&g_sem_empty, 0, 0);
//初始化这些信号量
pthread_mutex_init(&g_mutex, NULL);
//也可以用于多个进程间的不同线程之间进行通信,取决于第一个对象是不算在
//共享内存区,当前不是在共享内存区,表明只能在同一个进程的不同线程间通信
for(i = 0; i<CONSUMERS_COUNT; i++)
{
pthread_create(&g_thread[i], NULL, consume, (void*)i);
}
//创建若干个消费者线程
for(i = 0; i< PRODUCERS_COUNT; i++)
pthread_create(&g_thread[CONSUMERS_COUNT+i], NULL, produce, (void*)i);
//创建若干个生产者线程
for(i = 0; i< CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
pthread_join(g_thread[i], NULL);
//等待所创建的线程执行完成
sem_destroy(&g_sem_full);
sem_destroy(&g_sem_empty);
pthread_mutex_destroy(&g_mutex);
//销毁这些信号量和锁
return 0;
}
自旋锁与读写锁
自旋锁类似与互斥锁,但是,它的性能比互斥锁更高,
一般来说:对于实时性要求比较高的话,才会应用自旋锁
自旋锁跟互斥锁:一个很重要的区别在于,线程在申请自旋锁的时候,线程不会被挂起,即使线程不能得到锁,
它处于忙等待状态(也就是说:cpu处于忙碌的状态)不断的申请锁
所以说:自旋锁不能用于等待时间长的应用,一般来说应用与等待时间短的应用(一些实时性要求高的应用)
如果等待时间长的话,cpu处于等待的状态,在浪费cpu。
pthread_spin_init
pthread_spin_lock
pthread_spin_unlock
pthread_spin_destroy
读写锁
读写锁也是在跟互斥锁进行比较:只要没有线程持有给定的读写锁用于写,那么任意数目的线程可以持有读写锁用于
读,(也就是说:只要没有线程以写的方式锁定了该锁,那么其它线程可以以读的方式锁定该锁)
读写锁用于读的话称为共享锁,(也就是说:一个线程锁定了共享锁,那么其它线程也是可以持有共享锁的)
读写索用于写的话称为排它锁,
一个线程不管是读的锁,还是写的锁,这个时候不允许其它线程对该锁进行写的操作,只要有一个线程加了读写锁,
那么这个时候我们就不能在施加排它锁。