第三十篇,进程之间的通讯方式——内存共享,内存互斥关系——信号量,Linux下的最小资源单位——线程的详细讲解

一、进程之间的通信方式   ---  共享内存。
1、共享内存也是属于IPC对象,所以在使用共享内存之前一定要申请key值与ID号。
2、共享内存作用机制以及使用范围。
作用范围:由于共享内存是IPC对象,属于系统的资源,所以在整个系统中都可以看到IPC对象,其作用范围就是linux下任意的两个进程。
机制:任意一个进程可以将数据发送到共享内存上,另外的进程就可以访问内存上的数据。

3、实现共享内存的步骤:
1)先申请key值
   key = ftok(".",10);

2)根据申请到的key值去申请共享内存的ID号。   -->  shmget()  --> man 2 shmget
功能: allocates a System V shared memory segment
    //允许申请共享内存块

使用格式:
    #include <sys/ipc.h>
        #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);

参数:
    key:  key值
    size: 共享内存的总字节数,必须是PAGE_SIZE的倍数   #define PAGE_SIZE 1024
    shmflg: IPC_CREAT|0666   --> 不存在则创建

返回值:
    成功:共享内存ID号
    失败:-1

3)根据ID号去申请共享内存的区域。  --> shmat()  --> man 2 shmat
   
    #include <sys/types.h>
    #include <sys/shm.h>

   void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:
    shmid: 共享内存ID号
    shmaddr: NULL    --> 系统为你分配空间地址  99.99%
        不为NULL --> 由用户自己去分配      0.01%
    shmflg: 普通属性  -> 0

返回值:
    成功:共享内存的地址
    失败:-1

4)如何撤销空间?  --> shmdt()   --> man 2 shmdt

    #include <sys/types.h>
    #include <sys/shm.h>

   int shmdt(const void *shmaddr);

参数:
    shmaddr:共享内存的地址

返回值:
    成功:0
    失败:-1

5)删除共享内存IPC对象?   --> shmctl()   --> man 2 shmctl

    #include <sys/ipc.h>
    #include <sys/shm.h>

  int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:
    shmid:共享内存ID号
    cmd:IPC_RMID
    buf:如果是删除,则设置为NULL

返回值:
    成功:0
    失败:-1

    例题: 使用共享内存机制,来实现进程之间的通信。

写端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>

int main(int argc,char *argv[])
{
    //1. 申请key值
    key_t key = ftok(".",10);
    
    //2. 根据key值去申请共享内存的ID号
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    
    //3. 根据ID号去共享内存中申请空间
    char *p = shmat(shmid,NULL,0);
    if(p == (void *)-1)
        printf("shmat error!\n");
    
    //4. 不断往共享内存中写入数据
    while(1)
    {
        fgets(p,1024,stdin);
        
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    
    return 0;    
}


读端:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    //1. 申请key值
    key_t key = ftok(".",10);
    
    //2. 根据key值去申请共享内存的ID号
    int shmid = shmget(key,1024,IPC_CREAT|0666);
    
    //3. 根据ID号去共享内存中申请空间
    char *p = shmat(shmid,NULL,0);
    if(p == (void *)-1)
        printf("shmat error!\n");
    
    //4. 每隔2秒就打印一次
    while(1)
    {
        //sleep(2);
        printf("from shm:%s",p);
        
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    
    //5. 撤销映射
    shmdt(p);
    
    //6. 删除共享内存IPC对象
    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

运行结果:
如果写端将某一个数据放置到共享内存上,那么读端就会一直读取共享内存的数据,不会阻塞。   --> 数据践踏。

我们想要的目标:
发送一次    -->  打印一次
再发送一次  -->  再打印一次


二、处理共享内存互斥关系  ---   信号量。
1、什么是信号量?
信号量不是进程之间的通信方式,只是作用于进程之间的通信。
信号量通过管理空间+数据,实现进程对同一片共享内存的访问互斥关系。

信号量由于是IPC对象,所以必须要申请key值与ID号。

2、关于信号量函数接口?
1)先申请key值。
   key_t key = ftok(".",10);

2)根据key值去申请信号量的ID号。   --> semget()   --> man 2 semget
功能: semget - get a System V semaphore set identifier
    //获取信号量的ID号

使用格式:
    #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>

       int semget(key_t key, int nsems, int semflg);

参数:
    key:  key值
    nsems: 信号量中元素的个数    空间+数据  -> 2
    semflg: IPC_CREAT|0666  --> 不存在则创建


返回值:
    成功:信号量的ID号
    失败:-1

3)设置信号量初始值以及删除信号量。  -->   semctl()  --> man 2 semctl

使用格式:
    #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/sem.h>

       int semctl(int semid, int semnum, int cmd, ...);

参数:
    semid: 信号量ID号
    semnum:需要操作的成员的下标     想操作空间:0   想操作数据:1
    cmd:IPC_RMID   --> 用于删除信号量
         SETVAL     --> 用于设置信号量的起始值
    ...:如果第三个参数填SETVAL,那么就要填最后一个参数,这个参数就是起始值。


例如:我想设置空间为1,数据为0,代码如何写?
semctl(semid,0,SETVAL,1);  //空间为1
semctl(semid,1,SETVAL,0);  //数据为0

返回值:
    成功:0
    失败:-1

4)如何实现信号量的p/v操作?   (p操作:1->0   v操作:0->1)     --> semop()   --->  man 2 semop

   #include <sys/types.h>
   #include <sys/ipc.h>
   #include <sys/sem.h>

  int semop(int semid, struct sembuf *sops, size_t nsops);

参数:
    semid:  信号量ID号
    sops:

struct sembuf{
    unsigned short sem_num;  //需要操作的成员的下标       想操作空间:0   想操作数据:1
        short          sem_op;   //p操作/v操作                p操作:-1       v操作:1
        short          sem_flg;  //普通属性,填0
};
    nsops:   信号量操作结构体的个数  --> 1

返回值:
    成功:0
    失败:-1

例如:我想空间p操作,代码如何写?

struct sembuf space; //定义结构体变量
space.sem_num = 0;  //代表空间
space.sem_op = -1;  //代表p操作
space.sem_flg = 0;

semop(semid,&space,1);  //空间p操作

例如:我想数据v操作,代码如何写?

struct sembuf data;
data.sem_num = 1;  //代表数据
data.sem_op = 1;   //代表v操作
data.sem_flg = 0;  

semop(semid,&data,1);  //数据v操作


   例题: 已知shm/目录下的代码运行结果会出现数据践踏,请把信号量技术加入到shm/目录下的代码中,把数据践踏这个问题处理掉。
读端:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/sem.h>

int main(int argc,char *argv[])
{
    //1. 申请key值
    key_t key1 = ftok(".",10);
    key_t key2 = ftok(".",20);
    
    //2. 根据key值去申请共享内存和信号量的ID号
    int shmid = shmget(key1,1024,IPC_CREAT|0666);
    int semid = semget(key2,2,IPC_CREAT|0666);
    
    //3. 根据ID号去共享内存中申请空间
    char *p = shmat(shmid,NULL,0);
    if(p == (void *)-1)
        printf("shmat error!\n");
    
    //4 设置信号量的起始值
    semctl(semid,0,SETVAL,1);  //设置空间为1
    semctl(semid,1,SETVAL,0);  //设置数据为0
    
    //5. 数据p操作
    struct sembuf data;  
    data.sem_num = 1; //数据
    data.sem_op = -1;  //p操作
    data.sem_flg = 0; //普通属性
    
    //6. 空间v操作
    struct sembuf space;
    space.sem_num = 0;  //空间
    space.sem_op = 1;  //v操作
    space.sem_flg = 0;  //普通属性
    
    //7. 每隔2秒就打印一次
    while(1)
    {
        //请问数据能不能减1? //数据p操作
        semop(semid,&data,1); 
        
        printf("from shm:%s",p); //开车出来
        
        //车位的数量(空间)自动加1 
        semop(semid,&space,1);
        
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    
    //8. 撤销映射
    shmdt(p);
    
    //9. 删除共享内存IPC对象
    shmctl(shmid,IPC_RMID,NULL);
    
    //10. 删除信号量
    semctl(semid,0,IPC_RMID);

    return 0;
}


写端:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdio.h>
#include <sys/sem.h>

int main(int argc,char *argv[])
{
    //1. 申请key值
    key_t key1 = ftok(".",10);
    key_t key2 = ftok(".",20);
    
    //2. 根据key值去申请共享内存和信号量的ID号
    int shmid = shmget(key1,1024,IPC_CREAT|0666);
    int semid = semget(key2,2,IPC_CREAT|0666);
    
    //3. 根据ID号去共享内存中申请空间
    char *p = shmat(shmid,NULL,0);
    if(p == (void *)-1)
        printf("shmat error!\n");
    
    //4 设置信号量的起始值
    semctl(semid,0,SETVAL,1);  //设置空间为1
    semctl(semid,1,SETVAL,0);  //设置数据为0
    
    //5. 空间p操作
    struct sembuf space;
    space.sem_num = 0;  //空间
    space.sem_op = -1;  //p操作
    space.sem_flg = 0;  //普通属性
    
    //6. 数据v操作
    struct sembuf data;  
    data.sem_num = 1; //数据
    data.sem_op = 1;  //v操作
    data.sem_flg = 0; //普通属性
    
    //7. 不断往共享内存中写入数据
    while(1)
    {
        //请问空间能不能减1?
        semop(semid,&space,1);  //空间p操作
        
        fgets(p,1024,stdin); //开车进去
        
        //车的数量(数据)自动加1。  //数据v操作
        semop(semid,&data,1); //数据v操作
        
        
        if(strncmp(p,"quit",4) == 0)
        {
            break;
        }
    }
    
    return 0;    
}


三、linux下最小资源单位。  --  线程。
1、进程与线程的区别?
假设linux是一个社会,那么进程就是一个人,线程就是一个人的一只手。
线程其实就是一个进程的内部资源。

int main()
{
    /* 进程开始 */
    xxxxx;    
    xxxxx;   --> 在进程运行过程中,可以开启很多个线程。
    xxxxx;
    xxxxx;
    return 0;
    /* 进程结束 */
}

2、线程函数接口特点?
1)由于线程函数都是封装在线程库中,所以我们是看不到源码,查看线程函数的使用格式,都是在第3手册: man 3 xxx
2)所有线程函数头文件只有一个:#include <pthread.h>

四、线程的函数接口?
1、如何在一个进程中创建一个线程?   -->  pthread_create()   --> man 3 pthread_create
功能: pthread_create - create a new thread
        //创建一个新的线程

使用格式:
  

  #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

参数:
  

 thread: 存储线程ID号的空间的地址。
    attr:   线程的属性,普通属性填NULL。
    start_routine: 线程的例程函数    这个函数必须长: void *func(void *arg)
            (你想线程做什么事情,只需要把这些事情写入到这个函数中即可)
    arg:传递给线程例程函数的参数,如果不需要传递,则设置为NULL

返回值:
    成功:0
    失败:非0错误码

注意:    Compile and link with -pthread.
    //编译时要链接线程库

错误示范:gcc xxx.c -o xxx
正确示范:gcc xxx.c -o xxx -lpthread


大概:
void *func(void *arg)  //子线程的例程函数
{
    //线程开始
    ??????    --> 主线程与子线程的代码是同时运行的。
    ??????
        ??????


    //线程结束
}

int main()
{
    //进程开始

    pthread_create(xxx , xxx , func , xxx);
    //主线程
    ??????    --> 主线程与子线程的代码是同时运行的。
    ??????
    ??????

    return 0;  //进程结束
}


    例题: 尝试去创建一个子线程,看看能不能同时做两件事情。

//情况一: 子线程与主线程一起工作,子线程工作时间短,主线程工作时间长。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *arg)  //线程的工作函数  arg = NULL
{
    //现在这里就是子线程
    int i;
    for(i=0;i<5;i++)
    {
        sleep(1);
        printf("child i = %d\n",i);
    }
}

int main(int argc,char *argv[])
{
    //1. 马上创建一个子线程。
    pthread_t tid;
    pthread_create(&tid,NULL,func,NULL);
    
    //2. 现在这里就是主线程
    int i;
    for(i=0;i<10;i++)
    {
        sleep(1);
        printf("parent i = %d\n",i);
    }
    
    return 0;
}

运行结果:
子线程运行5s  主线程运行10s   程序正常退出。

//情况二: 子线程与主线程一起工作,子线程工作时间长,主线程工作时间短。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *arg)  //线程的工作函数  arg = NULL
{
    //现在这里就是子线程
    int i;
    for(i=0;i<10;i++)
    {
        sleep(1);
        printf("child i = %d\n",i);
    }
}

int main(int argc,char *argv[])
{
    //1. 马上创建一个子线程。
    pthread_t tid;
    pthread_create(&tid,NULL,func,NULL);
    
    //2. 现在这里就是主线程
    int i;
    for(i=0;i<5;i++)
    {
        sleep(1);
        printf("parent i = %d\n",i);
    }
    
    return 0;
}

运行结果:
主线程与子线程在5s时,一起结束了。


2、如何接合一个线程?   -->  pthread_join()   --> man 3 pthread_join
功能: pthread_join - join with a terminated thread
        //接合一个结束的线程

使用格式:
    #include <pthread.h>

       int pthread_join(pthread_t thread, void **retval);

参数:
    thread:需要接合的那个子线程的ID号
    retval:存储子线程退出值的指针,如果不关心子线程的退出状态只接合,那么就设置为NULL。

返回值:
    成功:0
    失败:非0错误码------------------------------------------------

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *arg)  //线程的工作函数  arg = NULL
{
    //现在这里就是子线程
    int i;
    for(i=0;i<10;i++)
    {
        sleep(1);
        printf("child i = %d\n",i);
    }
}

int main(int argc,char *argv[])
{
    //1. 马上创建一个子线程。
    pthread_t tid;
    pthread_create(&tid,NULL,func,NULL);
    
    //2. 现在这里就是主线程
    int i;
    for(i=0;i<5;i++)
    {
        sleep(1);
        printf("parent i = %d\n",i);
    }
    
    //3. 原地等待孩子的退出,接合他。
    pthread_join(tid,NULL);
    
    return 0;
} 


----------------------------------------------   

3、线程的退出?  --> pthread_exit()   --> man 3 pthread_exit
功能: pthread_exit - terminate calling thread
        //结束一个正在运行的线程

使用格式:
    #include <pthread.h>

       void pthread_exit(void *retval);

参数:
    retval: 存储着退出值的变量的地址     --> 这个退出值必须是全局变量,不能是局部变量。

返回值:无--------------------------------------------------

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int exit_state = 50;

void *func(void *arg)
{
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d\n",i);
        sleep(1);
    }
    
    //如果你想让主线程知道,你是怎么退出的,那么就得调用pthread_exit()函数。
    pthread_exit((void *)&exit_state);
}

int main(int argc,char *argv[])
{
    pthread_t tid;
    pthread_create(&tid,NULL,func,NULL);
    
    void *p = NULL;
    pthread_join(tid,&p);  //p = (void *)&exit_state
    printf("exit_state = %d\n",*(int *)p);
    
    return 0;
}


---------------------------------------------------


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖爱Kun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值