{C++系列} UINX高级编程 day16 线程同步 信号量 信号量集
in C/C++ with 0 comment

{C++系列} UINX高级编程 day16 线程同步 信号量 信号量集

in C/C++ with 0 comment

复习

一,线程同步

条件变量

有一种情况: 线程A阻塞等待某个条件为真,条件变为真,线程A继续执行。这时候线程B运行过程中使条件变为真,线程A解除阻塞,继续执行,这个变量称为条件变量。
linux系统为条件变量命名了类型,

pthread_cond_t为这个类型定义了一系列操作。

pthread_cond_t cond= PTHREAD_COND_INITIALIZER;

#include <pthread.h>
P_thread_cond_t
int     pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

功能:初始化一个条件变量
参数
cond:要初始化的条件变量
Attr:指定条件变量的属性。NULL(缺省的属性)
返回值
成功 0
错误吗 非0

int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁一个条件变量,释放资源
参数
Cond:指定了要销毁的条件变量。
返回值
成功 0
错误吗 非0

int     pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

功能:阻塞等待指定的时间,如果时间内没有变为真,返回错误。
参数
Cond:指定了阻塞等待的条件变量
mutex:指定了mutex锁
abstime:指定了等待的周期
返回值
成功 0
错误吗 非0

int     pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

功能:启动一个在条件变量上阻塞等待的线程。
参数
Cond:指定了阻塞等待的条件变量
Mutex:指定了mutex锁
步骤:

成功 0
错误吗 非0

int pthread_cond_broadcast(pthread_cond_t *cond);
功能:启动所有的在条件变量上阻塞等待的线程。
参数
cond:指定了阻塞等待的条件变量。
返回值
成功 0
错误吗 非0

举例:

使用条件变量实现生产者和消费者的例子

其中使用到数据结构的链表操作
生产者生产出一个节点以后,将节点插入到链表的的头部。
消费者从链表的头部取出一个节点进行消费

定义节点的类型

Typedef struct node  node_t;
Struct node{
    Int num;
    Node_t  *next;
};
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>

typedef struct node node_t;
struct node {
    int num;
    node_t *next;
};
typedef node_t *list_t;

list_t head=NULL;
//声明一把mutex锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
// 声明一个条件变量
pthread_cond_t  cond = \
            PTHREAD_COND_INITIALIZER;
//生产线程
void *product(void *arg){
    while(1){
        //创建一个新节点
        list_t new=(list_t)malloc(\
                sizeof(node_t));
        new->num=rand()%1000+1;
        printf("product:%d\n",new->num);
        //加锁
        pthread_mutex_lock(&mutex);
        //将新节点插入到链表的头部
        new->next=head;
        head=new;
        //解锁
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
        sleep(rand()%5);
    }
}


//消费线程
void *consume(void *arg){
    list_t tmp;
    while(1){
        //加锁
        pthread_mutex_lock(&mutex);
        while(!head)
            //阻塞等待head不为空
            pthread_cond_wait(&cond,&mutex);
    
        tmp=head;
        head=head->next;
        //解锁
        pthread_mutex_unlock(&mutex);
        //消费
        printf("consume:%d\n",tmp->num);
            
        free(tmp);
        tmp=NULL;
        sleep(rand()%5);
    }
}

int main(void){
    pthread_t pid,cid;
    srand(time(NULL));
    //创建两个线程,分别用于生产者和消费者
    pthread_create(&pid,NULL,product,NULL);
    pthread_create(&cid,NULL,consume,NULL);
    //等待线程汇合
    pthread_join(pid,NULL);
    pthread_join(cid,NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

信号量

系统对信号量类型的定义 sem_t
对这个类型封装的操作包括:

#include<semaphore.h>
Int sem_init()

功能:初始化信号量
参数
sem:要初始化的信号量的地址
Psaphore:
value:信号量的值
返回值:
成功 0
错误 -1 errno被设置

Sem_destroy(sem_t sem)
功能:销毁信号量
参数
Sem:信号量的地址
返回值
成功 0
错误 -1 errno被设置

Int sem_post(sem_t *sem)
功能:信号量的值加1.代表了线程释放资源
参数
Sem:指定要加1的信号量的地址
返回值:
成功 0
错误 -1 errno被设置 sem的值不变

Sem_wait();

int sem_wait(sem_t *sem);
功能:让信号量的值减1。如果当前信号量的值为0.阻塞等待
参数
Sem:指定要减1的信号量的地址
返回值:
成功 0
错误 -1 errno被设置 sem的值不变

举例

使用信号量实现生产者和消费者
使用到的数据结构是环状队列

 1 #include<stdio.h>
  2 #include<pthread.h>
  3 #include<time.h>
  4 #include<stdlib.h>
  5 #include<sys/types.h>
  6 #include<semaphore.h>
  7 #include<unistd.h>
  8
  9 int que[7];
 10 sem_t pro,con;
 11 void *product(void *arg){
 12         int r=0;
 13         while(1){
 14                 sem_wait(&pro);
 15                 que[r]=rand()%1000+1;
 16                 printf("p:%d\n",que[r]);
 17                 r=(r+1)%7;
 18                 sem_post(&con);
 19                 sleep(rand()%5);
 20         }
 21 }
 22
 23 void *consume(void *arg){
 24         int f=0;
 25         while(1){
 26                 sem_wait(&con);
 27                 printf("c:%d\n",que[f]);
 28                 que[f]=-1;
 29                 f=(f+1)%7;
 30                 sem_post(&pro);
 31                 sleep(rand()%5);
 32         }
 33 }
34 int main(){
 35         pthread_t pid,cid;
 36         //初始化两个信号量
 37         sem_init(&pro,0,7);
 38         sem_init(&con,0,0);
 39         //创建两个线程
 40         pthread_create(&pid,NULL,product,NULL);
 41         pthread_create(&cid,NULL,consume,NULL);
 42         //等待线程的汇合
 43         pthread_join(pid,NULL);
 44         pthread_join(pid,NULL);
 45         //销毁信号量
 46         sem_destroy(&pro);
 47         sem_destroy(&con);
 48         return 0;
 49 }

二,system V IPC
信号量集
信号量集就是信号量数组。这个集合里有多个信号量

#include <sys/sem.h>
int     semget(key_t key, int nsems, int semflg);

功能:获取一个信号量集的id
参数
Key: ftok(3)的返回值
Nsems:指定了信号量集中信号量的个数。
Semflg:
IPC_CREATE: 创建一个信号量
mode:指定了信号量集的权限
返回值:
-1 错误 errno被设置
成功返回信号量集的id

#include <sys/sem.h>
int     semctl(int semid, int semnum, int cmd, ...);

功能:信号量控制操作
参数
semid:指定了信号量集
semnum:指定了信号量集中的第几个信号量
Cmd:指定了控制操作的命令

    GETVAL:获取第几个信号量的semval值
    SETVAL:设置semval的值为arg.val

…:参数的个数和类型取决于cmd
返回值:
错误 -1 errno被设置
如果命令是GETVAL,成功 返回值为信号量semval的值
如果命令是SETVAL,成功 返回0

如果需要第四个参数,需要自己定义

     union semun {
             int     val;            /* value for SETVAL */
             struct  semid_ds *buf;  /* buffer for IPC_STAT & IPC_SET */
             u_short *array;         /* array for GETALL & SETALL */
     };

IPC_INFO

举例

使用信号量集 完成进程间的通讯
一个进程负责设置信号量集中的指定信号量的semval值
一个进程负责获取信号量集中指定信号量的semval值

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
typedef union semun{
    int val;
}semun_t;

int main(void){
    key_t key;
    semun_t arg;
    struct sembuf semb={0,-1,IPC_NOWAIT};
    //获取键值
    key=ftok(".",21);
    if(key==-1){
        perror("ftok");
        return -1;
    }
    //使用键值获取信号量集的id
    int semid=semget(key,1,IPC_CREAT|0664);
    if(semid==-1){
        perror("semget");
        return -1;
    }
    //设置arg的val值
    arg.val=5;
    //设置信号量集中第一个信号量的值为5.
    int s=semctl(semid,0,SETVAL,arg);
    if(s==-1){
        perror("semctl");
        return -1;
    }
    while(1){
        sleep(3);
        int sp=semop(semid,&semb,1);
        if(sp==-1){
            perror("semop");
            return -1;
        }
    }
    return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int main(void){
    key_t key;
    //获取键值
    key=ftok(".",21);
    if(key==-1){
        perror("ftok");
        return -1;
    }
    //使用键值获取信号量集的id
    int semid=semget(key,1,IPC_CREAT|0664);
    if(semid==-1){
        perror("semget");
        return -1;
    }
    while(1){
        //设置信号量集中第一个信号量的值为5.
        int s=semctl(semid,0,GETVAL);
        if(s==-1){
            perror("semctl");
            return -1;
        }
        printf("s=%d\n",s);
        sleep(1);
    }
    return 0;
}
 #include <sys/sem.h>
int     semop(int semid, struct sembuf *sops, size_t nsops);

功能:信号量操作
参数
semid:指定了信号量集的id
Sops:指定了对单一信号量的操作
nsops:操作的信号量的个数
返回值

struct sembuf {
             u_short sem_num;        /* semaphore # */
             short   sem_op;         /* semaphore operation */
             short   sem_flg;        /* operation flags */
     };

Sem_num:指定了第几个信号量
sem_op:

0 semop+semval的值 释放资源
=0 等待到0,立即执行。如果不等于0,IPC_NOWAIT被指定,立即返回错误。

<0 if(semva >= |semop|) semval+semop;其实质是减去sem_op的绝对值。

        `If(|semop| >semval )`    `IPC_NOWAIT`被指定    失败    errno被设置;如果`IPC_NOWAIT`未指定,阻塞等待                                                          `semval>=sem_op`

Sem_flg:

    IPC_NOWAIT:非阻塞等待
    0        成功

三,web服务器的实现
采用http协议

Responses