信号量

信号量称为进化版的互斥锁。由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。例如,有5台打印机被多个线程共同使用,如果使用互斥锁,则每次同时只能有一个打印机被一个线程使用,但是如果采用信号量,那么同时最多可有5个线程共同使用五台打印机,使得共享资源得到充分利用,程序并行性提高,效率更高。其实信号量初值为1时的情况就是互斥锁。信号量是互斥锁的加强版。

主要应用函数:

sem_init函数          sem_destroy函数            sem_wait函数

sem_trywait函数            sem_timedwait函数          sem_post函数

以上6 个函数的返回值都是:成功返回0,失败返回-1,同时设置errno(注意,它们没有pthread前缀)  因为,信号量既可以用于线程间同步,也可以用于进程间同步,因此失败返回值为-1,同时置errno。

sem_t类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。

sem_t sem;  规定信号量sem不能 < 0。 头文件

1)信号量基本操作

sem_wait: 信号量大于0,则信号量--(类比pthread_mutex_lock);信号量等于0,造成线程阻塞

sem_post将信号量++,同时唤醒阻塞在信号量上的线程(类比pthread_mutex_unlock)。

由于sem_t的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。信号量的初值,决定了占用信号量的线程的个数

2sem_init函数

int sem_init(sem_t *sem, int pshared, unsigned int value);

作用:初始化一个信号量。参1:sem信号量;参2:pshared0用于线程间;取非0(一般为1)用于进程间;参3:value指定信号量初值。

3sem_destroy函数

int sem_destroy(sem_t *sem);

作用:销毁一个信号量

4sem_wait函数

int sem_wait(sem_t *sem);

作用:给信号量加锁

5sem_post函数

int sem_post(sem_t *sem);   

作用:给信号量解锁 ++

6sem_trywait函数

int sem_trywait(sem_t *sem);      

作用:尝试对信号量加锁--(与sem_wait的区别类比lock和trylock)

7sem_timedwait函数

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

作用:限时尝试对信号量加锁。参2:abs_timeout采用的是绝对时间(同前面)。

 

总结:互斥量、信号量既可以用于进程间同步,也可以用于线程间同步(一般来说,进程间同步信号量用的多一点);条件变量需要结合互斥量使用;读写锁用于线程间同步,而文件锁用于进程间同步。

 

8)生产者消费者信号量模型

使用信号量完成线程间同步,模拟生产者,消费者问题。             

分析:如果仓库中装满了产品,生产者不能生产,只能阻塞;仓库中没有产品,消费者不能消费,只能等待数据。

//两个消费者,一个生产者的情况

#include 
#include 
#include 
#include 
#include 
#include 


#define NUM 10    //仓库空间容量为10

int prdc[NUM] = {0};   //0值代表该位置为闲置(空)
sem_t sem_blank;   //定义一个信号量表示闲置的空间
sem_t sem_prdc;   //定义一个信号量表示产品的数量
int j=0;       //两个消费者都需要消耗产品,因此定义全局变量j,两消费者互斥访问

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  //互斥锁 用于互斥访问j


void *productor( void *arg )   //生产者
{
        srand( time( NULL ) );
        int i = 0;

        while(1) {
            sem_wait( &sem_blank );
            prdc[i] = (rand( )%400+1);
            printf("++++++++The production is %d.\n", prdc[i]);
            sem_post( &sem_prdc );  //对全局变量的操作必须位于wait与post之间
            i=( (i+1)%NUM );

            sleep( rand( )%3 );
        }

        return NULL;
}

void *consumer( void *arg )
{
        int s = (int)arg;
        srand( time( NULL ) );

        while(1) {
            sem_wait( &sem_prdc );
            pthread_mutex_lock( &mutex );  //互斥访问j
            printf("I am the %dth consumer, and I consumed the product of %d.\n", s, prdc[j]);
            prdc[j] = 0;
            j =( (j+1)%NUM );
            pthread_mutex_unlock( &mutex );
            sem_post( &sem_blank );

            sleep( rand( )%3 );
        }

        return NULL;
}

int main( void )
{
        pthread_t pid, cid1, cid2;
        int ret;

        sem_init( &sem_blank, 0, NUM );
        sem_init( &sem_prdc, 0, 0 );

        ret = pthread_create( &pid, NULL, productor, NULL );
        if( ret != 0 )
        {
            fprintf( stderr, "pthread_create error1: %s.\n", strerror(ret) );
            exit(1);
        }
        ret = pthread_create( &cid1, NULL, consumer, (void *)1 );
        if( ret != 0 )
        {
            fprintf( stderr, "pthread_create error2: %s.\n", strerror(ret) );
            exit(1);
        }
        ret = pthread_create( &cid2, NULL, consumer, (void *)2 );
        if( ret != 0 )
        {
            fprintf( stderr, "pthread_create error3: %s.\n", strerror(ret) );
            exit(1);
        }

        pthread_join( pid, NULL );
        pthread_join( cid1, NULL );
        pthread_join( cid2, NULL );

        sem_destroy( &sem_blank );
        sem_destroy( &sem_prdc );
        pthread_mutex_destroy( &mutex);

        return 0;
}

[root@localhost 02_pthread_sync_test]# ./semaphore

++++++++The production is 331.

I am the 2th consumer, and I consumed the product of 331.

++++++++The production is 371.

I am the 1th consumer, and I consumed the product of 371.

++++++++The production is 213.

I am the 1th consumer, and I consumed the product of 213.

++++++++The production is 260.

I am the 2th consumer, and I consumed the product of 260.

++++++++The production is 296.

I am the 1th consumer, and I consumed the product of 296.

++++++++The production is 59.

++++++++The production is 118.

I am the 2th consumer, and I consumed the product of 59.

I am the 1th consumer, and I consumed the product of 118.

++++++++The production is 228.

I am the 1th consumer, and I consumed the product of 228.

++++++++The production is 27.

++++++++The production is 360.

++++++++The production is 190.

I am the 2th consumer, and I consumed the product of 27.

I am the 2th consumer, and I consumed the product of 360.

I am the 1th consumer, and I consumed the product of 190.

++++++++The production is 177.

++++++++The production is 211.

I am the 1th consumer, and I consumed the product of 177.

I am the 2th consumer, and I consumed the product of 211.

分析:

  1. 注意一点:生产者从i=0开始生产,那么消费者必须从j=0位置开始消费,保证位置一一对应,如果不对应,从逻辑上就是错误的。即整个逻辑过程就是:生产者在队列头部加产品,消费者从队列尾部拿走产品,整个队列从逻辑上是一个循环队列。
  2. 对j的访问要互斥访问;
  3. 从结果可以看出,先生产出来的先被消费,符合逻辑。

练习:结合生产者消费者信号量模型,揣摩sem_timedwait函数作用。编程实现,一个线程读用户输入, 另一个线程打印“hello world”。如果用户无输入,则每隔5秒向屏幕打印一个“hello world”;如果用户有输入,立刻打印“hello world”到屏幕。    

#include 
#include 
#include 
#include 

#define N 1024

sem_t s;

void *tfn(void *arg)
{
        char buf[N];

        while (1) {
                read(STDIN_FILENO, buf, N);   //阻塞读
                printf( " %s", buf);
                sem_post(&s);
        }

        return NULL;
}

int main(void)
{
        pthread_t tid;
        struct timespec t = {0, 0};

        sem_init(&s, 0, 0);
        pthread_create(&tid, NULL, tfn, NULL);
        t.tv_sec = time(NULL) + 1;   //防止时间失效,使sem_timedwait参数无效
        t.tv_nsec = 0;

        while (1) {
                sem_timedwait(&s, &t);
                printf(" hello world\n");
                t.tv_sec = time(NULL) + 5;
                t.tv_nsec = 0;
        }

        pthread_join(tid, NULL);
        sem_destroy(&s);

        return 0;
}

分析:

理解read函数的作用。read系统调用从文件中读数据时,默认方式为阻塞读,即如果没有读取数据(没有遇到换行符(enter键或者结束符)),则会一直阻塞等待数据的到来。对于空文件,有结束符,因此read读取时直接返回结果,读取的字节数为0,结束符不会打印出来;对于从标准输入(键盘)读取数据时,只有按到了enter(换行符),则read才会读取到数据,而换行符会被打印出来。read函数阻塞的话,则信号量的值一直为0,则主控线程中每隔5s打印一次hello world;(此时sem_timedwait函数执行失败,返回-1,置errno)。一旦read读取了数据,则信号量加1,主控线程马上执行sem_timedwait(&s, &t);成功,返回0 ,且向屏幕打印一次hello world。

你可能感兴趣的:(Linux系统编程,Linxu系统编程)