• 系统编程09-总结


    目录

    一、进程(重理论)

    1.进程的创建、退出、结合

    2.孤儿进程和僵尸进程

    3.fork和vfork的区别

    4.exec函数簇----system("tftp -g -r 1.txt 192.168.10.18")

    5.进程之间通信方式之管道

    1).无名管道--父子进程

    2).有名管道--任意进程

    6.进程之间的IPC(inter process communicate)重点

    3).消息队列

    4).共享内存

    5).信号量

    7.信号的发送接受

    1)发送

    2)捕捉信号(接收)

    3)挂起进程

    4)自己给自己发送信号

    5)信号集

    8.守护进程

    二、线程(重实操)1.创建、退出、结合

    2.主线程与子线程之间的通信方式

    1)全局变量2)通过传参数的方式3)通过pthread_exit的退出值

    3.线程的属性

    1)方法一:在创建子线程之前添加分离属性

    2)方法二:在子线程里面将自己设置为分离属性

    4.线程的取消(给线程发送一个取消信号)

    1)能取消

    2)在能取消的情况下延迟取消(遇到取消点函数的时候取消)

    3)压栈线程与弹栈线程

    5.线程之间的同步与互斥一

    1)信号量      -> 进程  (共享内存+信号量一起使用) 

    2)有名信号量  -> 进程     (共享内存+有名信号量一起使用) <编译的时候需要链接线程库-lpthread>

    3)无名信号量  -> 线程

    6.线程之间的同步与互斥二

    1)互斥锁      -> 线程                    ----->重点

    2)读写锁      -> 线程                    ---->重点

    3)条件变量    -> 线程(互斥锁+条件变量一起使用)  ---->重点


    一、进程(重理论)

    1.进程的创建、退出、结合

    fork()
    exit()
    wait() //获取子进程退出的状态值
    如果父进程想要接受子进程的退出的状态值,子进程就需要使用exit()函数

    1. int main(int argc,char **argv)
    2. {
    3. pid_t id;
    4. id = fork();
    5. if(id == -1)
    6. {
    7. //perror("fork fail");
    8. printf("fork fail\n");
    9. return -1;
    10. }
    11. else if(id > 0)//父进程
    12. {
    13. sleep(2);
    14. printf("[%d]我是你爹 正在打牌 儿子的ID号:%d\n",getpid(),id);
    15. wait(NULL);//阻塞等待子进程的退出,并回收它的资源
    16. }
    17. else if(id == 0)//子进程
    18. {
    19. printf("[%d]我是你的好大儿 出去浪 我爹的id号:%d\n",getpid(),getppid());
    20. }
    21. return 0;
    22. }

    2.孤儿进程和僵尸进程

    孤儿进程:父进程先退出,子进程还在;孤儿进程的资源它是由系统来回收。
    僵尸进程:父进程还在一直在干自己的事,子进程先退出;父进程没有回收它的资源。

    3.fork和vfork的区别

    fork父进程与子进程 拥有独立的内存空间,互不影响。
    vfork子进程与父进程共享内存空间,更加准确来说,子进程在调用exec函数或者exit函数之前内存空间是共享的

    1. int main()
    2. {
    3. //原本进程的代码
    4. int a = 100; //a是局部变量,是栈区
    5. //static int a = 100; //a是静态变量,存放在数据段
    6. printf("main\n");
    7. //使用vfork创建一个子进程,父进程与子进程共享数据段
    8. pid_t id = vfork();
    9. if(id == -1)//出错
    10. {
    11. printf("fork error\n");
    12. return -1;
    13. }
    14. else if(id >0)//父进程
    15. {
    16. printf("parent:%d a addr:%p value:%d\n",getpid(),&a,a);
    17. }
    18. else if(id == 0)//子进程
    19. {
    20. a = 250; //子进程修改变量a的值
    21. printf("child:%d a addr:%p value:%d\n",getpid(),&a,a);
    22. sleep(5);
    23. exit(0);//让子进程到这里就结束
    24. }
    25. printf("111\n");
    26. //阻塞等待 子进程退出
    27. wait(NULL);
    28. return 0;
    29. }

    4.exec函数簇----system("tftp -g -r 1.txt 192.168.10.18")

    第一种方法:
    execl("./a.out", "a.out", "abcd", NULL);  相当于跑了一条指令./a.out abcd
    第二种方法:
    char *const argv[ ] = {"hello","1.c","2.c",NULL};
    execv("./hello", argv);
    说明:
    l->列表 v->argv p->指定路径 e->自定义路径

    5.进程之间通信方式之管道

    1).无名管道--父子进程

    fd[1]为写入端(入队),fd[0]为读出端(出队)
    pipe()

    1. int main(int argc, char * argv [ ])
    2. {
    3. //创建一个数组
    4. int fd[2]; //fd[0]读 fd[1]写
    5. //创建管道
    6. pipe(fd);
    7. printf("fd[0]:%d fd[1]:%d\n",fd[0],fd[1]);
    8. pid_t id;
    9. id = fork();
    10. if(id > 0)
    11. {
    12. //父进程从管道给老大儿打钱
    13. write(fd[1],"money 100",strlen("money 100"));
    14. //wait(NULL);
    15. }
    16. else if(id == 0)
    17. {
    18. //老大儿从管道拿钱
    19. //sleep(1);
    20. char buf[1024] = {0};
    21. int ret = read(fd[0],buf,1024);
    22. printf("buf:%s ret=%d\n",buf,ret);
    23. }
    24. wait(NULL);//父进程阻塞等待子进程退出
    25. return 0;
    26. }


    2).有名管道--任意进程

    mkfifo()
    管道特点:
    对于读数据来说,管道里面有什么就拿什么    
    无名管道和有名管道都是通过系统IO函数来操作 open read write

    write

    1. #define FIFO_FILE "/home/gec/fifo2" //有名管道
    2. int main(int argc, char * argv [ ])
    3. {
    4. int ret;
    5. //先判断文件是否存在,如果存在则不用创建了
    6. if(access(FIFO_FILE,F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
    7. {
    8. ret = mkfifo(FIFO_FILE,0777);
    9. if(ret == -1)
    10. {
    11. perror("mkfifo fail");
    12. return -1;
    13. }
    14. }
    15. //打开管道
    16. int fd;
    17. fd = open(FIFO_FILE,O_RDWR);
    18. if(fd < 0)
    19. {
    20. perror("open fifo fail");
    21. return -1;
    22. }
    23. while(1)
    24. {
    25. sleep(1);
    26. write(fd,"money 250",strlen("money 250"));
    27. }
    28. return 0;
    29. }

    read 

    1. #define FIFO_FILE "/home/gec/fifo2" //有名管道
    2. int main(int argc, char * argv [ ])
    3. {
    4. int ret;
    5. //先判断文件是否存在,如果存在则不用创建了
    6. if(access(FIFO_FILE,F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
    7. {
    8. ret = mkfifo(FIFO_FILE,0777);
    9. if(ret == -1)
    10. {
    11. perror("mkfifo fail");
    12. return -1;
    13. }
    14. }
    15. //打开有名管道
    16. int fd;
    17. fd = open(FIFO_FILE,O_RDWR);
    18. if(fd < 0)
    19. {
    20. perror("open fifo fail");
    21. return -1;
    22. }
    23. char buf_recv[1024] = {0};
    24. while(1)
    25. {
    26. memset(buf_recv,0,sizeof(buf_recv));
    27. ret = read(fd,buf_recv,sizeof(buf_recv));
    28. printf("buf_recv:%s,ret:%d\n",buf_recv,ret);
    29. }
    30. return 0;
    31. }

    6.进程之间的IPC(inter process communicate)重点

    3).消息队列

    对于消息队列来说,我可以通过数据的编号,来拿取对应的数据
    ftok(路径)、msgget(文件描述符)、msgsnd、msgrcv

    练习1:
    将无名、有名、消息队列的代码跑一遍

    msg_snd

    1. //进程1 : 往消息队列中发送数据
    2. //自己定义的消息队列的数据结构体
    3. struct msgbuf{
    4. long mtype;//数据的编号/类型
    5. char mtext[1024]; //数据的正文
    6. };
    7. int main()
    8. {
    9. //1、确定 文件路径名 ---获取消息队列的key
    10. key_t key = ftok(".", 100);
    11. //2、根据文件路径名,打开文件 文件不存在则创建 得到fd ---根据key值 获取消息队列的ID,如果消息队列不存在则创建
    12. int msgid = msgget(key,IPC_CREAT|0666);
    13. if(msgid == -1){
    14. perror("msgget error");
    15. return -1;
    16. }
    17. printf("消息队列 key:%#x msgid:%d\n",key,msgid); //0x
    18. //3、往文件中写入数据 --------往消息队列中 发送数据
    19. struct msgbuf data;
    20. memset(&data,0,sizeof(data));
    21. strcpy(data.mtext,"nihao");//字符数组拷贝
    22. data.mtype = 10;
    23. struct msgbuf data2;
    24. memset(&data2,0,sizeof(data2));
    25. strcpy(data2.mtext,"nihao2");//字符数组拷贝
    26. data2.mtype = 20;
    27. while(1)
    28. {
    29. int ret = msgsnd(msgid, &data,strlen(data.mtext),0); //发送数据报文1
    30. if(ret == -1)
    31. {
    32. perror("msgsnd error");
    33. return -1;
    34. }
    35. msgsnd(msgid, &data2,strlen(data2.mtext),0); //发送数据报文2
    36. }
    37. return 0;
    38. }

    msg_recv

    1. //进程2 : 从消息队列中 获取数据
    2. //自己定义的消息队列的数据结构体
    3. struct msgbuf{
    4. long mtype;//数据的编号/类型
    5. char mtext[1024]; //数据的正文
    6. };
    7. int main()
    8. {
    9. //1、确定 文件路径名 ---获取消息队列的key
    10. key_t key = ftok(".", 100);
    11. //2、根据文件路径名,打开文件 文件不存在则创建 得到fd ---根据key值 获取消息队列的ID,如果消息队列不存在则创建
    12. int msgid = msgget(key,IPC_CREAT|0666);
    13. if(msgid == -1)
    14. {
    15. perror("msgget error");
    16. return -1;
    17. }
    18. printf("消息队列 key:%#x msgid:%d\n",key,msgid); //0x
    19. //3、往文件中写入数据 --------往消息队列中 发送数据
    20. struct msgbuf data;
    21. memset(&data,0,sizeof(data));
    22. while(1)
    23. {
    24. int ret = msgrcv(msgid, &data, sizeof(data.mtext),20,0); //只接受数据报文2
    25. printf("msgrcv ret :%d data:%s\n",ret,data.mtext);
    26. sleep(1);
    27. }
    28. //4、最后不需要 使用到这条消息队列的时候,删除即可
    29. msgctl(msgid, IPC_RMID, NULL);
    30. return 0;
    31. }

    4).共享内存

    ftok、shmget、shmat(地址映射)、shmdt(解除)、shmctl(删除)、直接在这个地址里面进行内存拷贝

    shm_wr

    1. //进程1--写端
    2. int main()
    3. {
    4. //1、申请key
    5. key_t key = ftok(".",10);
    6. //2、根据key值, 得到物理共享内存的ID号,如果该物理内存不存在,则创建 ---open
    7. int shmid = shmget(key,1024,IPC_CREAT|0666);
    8. printf("共享内存 key:%#x shmid:%d\n",key,shmid);
    9. //3、将物理内存映射到用户的虚拟内存空间中的某一块区域(类似于mmap的功能)
    10. char*shm_p = shmat(shmid,NULL,0);
    11. if(shm_p == (void*)-1)
    12. {
    13. perror("shmat error");
    14. return -1;
    15. }
    16. //4、直接往这块内存进行赋值(写入数据)
    17. //可以不断从键盘上获取数据,当输入exit的时候退出
    18. char sendbuf[1024] = "hello world";
    19. memcpy(shm_p,sendbuf, strlen(sendbuf)); //内存拷贝的操作
    20. while(1);
    21. //5、最后不用的时候解除 映射
    22. shmdt(shm_p);
    23. return 0;
    24. }

    shm_rd 

    1. //进程2
    2. int main()
    3. {
    4. //1、申请key
    5. key_t key = ftok(".",10);
    6. //2、根据key值, 得到物理共享内存的ID号,如果该物理内存不存在,则创建 ---open
    7. int shmid = shmget(key,1024,IPC_CREAT|0666);
    8. printf("共享内存 key:%#x shmid:%d\n",key,shmid);
    9. //3、将物理内存映射到用户的虚拟内存空间中的某一块区域
    10. char*shm_p = shmat(shmid,NULL,0);
    11. if(shm_p == (void*)-1)
    12. {
    13. perror("shmat error");
    14. return -1;
    15. }
    16. while(1)
    17. {
    18. //4、直接往这块内存读取数据
    19. //不断地读取数据,当获取到exit的时候退出
    20. printf("%s\n",shm_p);//从共享内存里面读数据
    21. }
    22. while(1);
    23. //4、最后不用的时候解除 映射
    24. shmdt(shm_p);
    25. //5、删除共享内存
    26. shmctl(shmid,IPC_RMID,NULL);
    27. return 0;
    28. }

    5).信号量

    >它不能单独使用,是和共享内存一起使用
    >操作流程如下
    初始化共享内存
    初始化信号量
    进程一:写
     空间P操作
     写数据
     数据V操作
    进程二:读
     数据P操作
     读数据
     空间V操作
    练习2:
     跑一下信号量的代码,理解它的流程

    shm_sem_wr

    1. int main()
    2. {
    3. //1、获取key
    4. key_t key = ftok(".",10);
    5. //2、根据key值 获取共享内存的ID号
    6. int shmid = shmget(key,1024,IPC_CREAT|0666);
    7. //3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
    8. char*shm_p = shmat(shmid,NULL,0);
    9. if(shm_p == (void*)-1)
    10. {
    11. perror("shmat error");
    12. return -1;
    13. }
    14. //4、获取信号量的key
    15. key_t key1 = ftok(".",20);
    16. //5、根据key值申请信号量ID号
    17. int semid = semget(key1,2,IPC_CREAT|0666);
    18. //6、初始化信号量起始值 (semnum:需要操作的成员的下标 空间:0 数据:1)
    19. semctl(semid,0,SETVAL,1);//设置空间的起始值为1 //有空间
    20. semctl(semid,1,SETVAL,0);//设置数据的起始值为0 //无数据
    21. //空间结构体初始化
    22. struct sembuf space;
    23. space.sem_num = 0;//空间
    24. space.sem_op = -1;//P操作
    25. space.sem_flg = 0;//普通属性
    26. //数据结构体初始化
    27. struct sembuf data;
    28. data.sem_num = 1;//数据
    29. data.sem_op = 1;//V操作
    30. data.sem_flg = 0;//普通属性
    31. //此时映射出来的shm_p 就是两个进程的共享内存
    32. while(1)
    33. {
    34. //空间:1 数据:0
    35. //开车进去之前,空间 -1 --P操作
    36. semop(semid, &space, 1);//请问空间能不能P操作?
    37. //能 -> 有车位 -> 函数返回
    38. //不能 -> 没车位 -> 函数阻塞
    39. //开车进去
    40. //从键盘上获取数据,存储到共享内存shm_p
    41. scanf("%s",shm_p);
    42. //开车进去之后,数据+1 --V操作
    43. semop(semid, &data, 1); //数据自动+1
    44. //退出条件,这里要注意 应该使用strncmp 指定字节数
    45. if(strncmp(shm_p,"exit",4) == 0)
    46. break;
    47. }
    48. //4、当不再使用时,解除映射关系
    49. shmdt(shm_p);
    50. //5、当没有进程再需要使用这块共享内存时,删除它
    51. shmctl(shmid, IPC_RMID, NULL);
    52. semctl(semid,0,IPC_RMID);
    53. return 0;
    54. }

     shm_sem_rd

    1. int main()
    2. {
    3. //1、获取key
    4. key_t key = ftok(".",10);
    5. //2、根据key值 获取共享内存的ID号
    6. int shmid = shmget(key,1024,IPC_CREAT|0666);
    7. //3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
    8. char*shm_p = shmat(shmid,NULL,0);
    9. if(shm_p == (void*)-1)
    10. {
    11. perror("shmat error");
    12. return -1;
    13. }
    14. //4、获取信号量的key
    15. key_t key1 = ftok(".",20);
    16. //5、根据key值申请信号量ID号
    17. int semid = semget(key1,2,IPC_CREAT|0666);
    18. //6、初始化信号量起始值
    19. semctl(semid,0,SETVAL,1);//设置空间的起始值为1
    20. semctl(semid,1,SETVAL,0);//设置数据的起始值为0
    21. //空间结构体初始化
    22. struct sembuf space;
    23. space.sem_num = 0;
    24. space.sem_op = 1; //V操作
    25. space.sem_flg = 0;
    26. //数据结构体初始化
    27. struct sembuf data;
    28. data.sem_num = 1;
    29. data.sem_op = -1;//P操作
    30. data.sem_flg = 0;
    31. //此时映射出来的shm_p 就是两个进程的共享内存
    32. while(1)
    33. {
    34. //空间:0 数据:1
    35. //把车开出来之前,请问数据能不能-1?
    36. semop(semid, &data, 1);
    37. //能 -> 里面有车 -> 函数返回
    38. //不能 -> 里面没车 -> 函数阻塞
    39. //从车库里面把车开出来
    40. //从共享内存中读取数据
    41. printf("recv:%s\n",shm_p);
    42. //sleep(1);
    43. //把车开出来之后,空间+1
    44. semop(semid, &space, 1);
    45. //空间:1 数据:0
    46. //退出条件,这里要注意 应该使用strncmp 指定字节数
    47. if(strncmp(shm_p,"exit",4) == 0)
    48. break;
    49. }
    50. return 0;
    51. }

    7.信号的发送接受

    1)发送

    int kill(pid_t pid, int sig);
    kill -9 进程id
    killall -9 进程名


    2)捕捉信号(接收)

    signal(函数句柄)
    sighandler_t signal(int signum, sighandler_t handler);


    3)挂起进程

    int pause(void);


    4)自己给自己发送信号

    raise()


    5)信号集

    添加信号的阻塞属性

    8.守护进程

    守护进程(Daemon)也被翻译为精灵进程、后台进程,是一种旨在运行于相对干净环境、不受终端影响的、
    常驻内存的进程,就像神话中的精灵拥有不死的特性,长期稳定提供某种功能或服务。


    二、线程(重实操)
    1.创建、退出、结合

    pthread_create()--->函数指针(回调函数)
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
    pthread_exit() 
    void pthread_exit(void *retval);

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

    1. int data = 300;
    2. void *fun(void *arg)
    3. {
    4. //通过传参拿到主线程给子线程传的值
    5. int num = *((int *)arg);
    6. printf("%d\n",*((int *)arg));
    7. //pthread_exit("hello");//"hello"是char *类型
    8. pthread_exit((void *)&data);
    9. }
    10. int main(int argc,char **argv)
    11. {
    12. int num = 250;
    13. //&num是int *类型;int *是void *类型中的一种
    14. int ret;
    15. pthread_t tid;
    16. ret = pthread_create(&tid,NULL,fun,&num);
    17. if(ret != 0)
    18. {
    19. printf("pthread_create fail\n");
    20. return -1;
    21. }
    22. void *retval;
    23. pthread_join(tid,&retval);
    24. //printf("retval = %s\n",(char *)retval);
    25. printf("retval = %d\n",*((int *)retval));
    26. return 0;
    27. }

    2.主线程与子线程之间的通信方式

    1)全局变量
    2)通过传参数的方式
    3)通过pthread_exit的退出值

    练习3:通过传参的方式将一个学生的信息传给子线程
    struct st
    {
        char name[20];
        char sex;
        int hight;
        float score;
    };

    1. struct st
    2. {
    3. char name[20];
    4. char sex;
    5. int hight;
    6. float score;
    7. };
    8. int data = 300;
    9. void *fun(void *arg)
    10. {
    11. //用结构体变量接收
    12. //struct st s = *(struct st *)arg;
    13. //printf("%-20s%-5c%-10d%-10.2f\n",s.name,s.sex,s.hight,s.score);
    14. //用结构体指针变量接收
    15. struct st *s = (struct st *)arg;
    16. printf("%-20s%-5c%-10d%-10.2f\n",s->name,s->sex,s->hight,s->score);
    17. pthread_exit((void *)&data);
    18. }
    19. int main(int argc,char **argv)
    20. {
    21. struct st s = {"bling",'M',188,98.5};
    22. //&s是struct st *类型;struct st *是void *类型中的一种
    23. int ret;
    24. pthread_t tid;
    25. ret = pthread_create(&tid,NULL,fun,&s);
    26. if(ret != 0)
    27. {
    28. printf("pthread_create fail\n");
    29. return -1;
    30. }
    31. void *retval;
    32. pthread_join(tid,&retval);
    33. //printf("retval = %s\n",(char *)retval);
    34. printf("retval = %d\n",*((int *)retval));
    35. return 0;
    36. }

    3.线程的属性

    1)方法一:在创建子线程之前添加分离属性

    pthread_attr_t attr;
    int pthread_attr_init(pthread_attr_t *attr); //初始化
    int pthread_attr_destroy(pthread_attr_t *attr); //销毁
    pthread_create(&tid,&attr,.....);


    2)方法二:在子线程里面将自己设置为分离属性

    pthread_detach(pthread_self());

    4.线程的取消(给线程发送一个取消信号)

    pthread_cancel()


    1)能取消

    2)在能取消的情况下延迟取消(遇到取消点函数的时候取消)

    pthread_setcanselstate();//能不能
    pthread_setcanseltype();//立即还是延迟取消


    3)压栈线程与弹栈线程

    压栈线程的取消例程函数-> pthread_cleanup_push()
    #include
    void pthread_cleanup_push(void (*routine)(void *),void *arg);
    弹栈线程的取消例程函数  --> pthread_cleanup_pop()
    #include
    void pthread_cleanup_pop(int execute);

    5.线程之间的同步与互斥一

    1)信号量      -> 进程  (共享内存+信号量一起使用) 

    空间+数据一起操作(很麻烦)

    2)有名信号量  -> 进程     (共享内存+有名信号量一起使用) <编译的时候需要链接线程库-lpthread>

    数据(比信号量要简单)
    sem_wait() //P操作
    sem_post() //V操作

    sem1

    1. #define SEM_NAME "/semname1" //要以"/"开头
    2. int main()
    3. {
    4. //1、获取key
    5. key_t key = ftok(".",10);
    6. //2、根据key值 获取共享内存的ID号
    7. int shmid = shmget(key,1024,IPC_CREAT|0666);
    8. //3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
    9. char*shm_p = shmat(shmid,NULL,0);
    10. if(shm_p == (void*)-1)
    11. {
    12. perror("shmat error");
    13. return -1;
    14. }
    15. //4、创建并打开一个有名信号量
    16. sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0);
    17. if(nameSem == SEM_FAILED )
    18. {
    19. printf("sem_open error\n");
    20. return -1;
    21. }
    22. //此时映射出来的shm_p 就是两个进程的共享内存
    23. while(1)
    24. {
    25. //从键盘上获取数据,存储到共享内存shm_p
    26. scanf("%s",shm_p);
    27. //有名信号量的V操作 数据 +1
    28. sem_post(nameSem);
    29. //退出条件,这里要注意 应该使用strncmp 指定字节数
    30. if(strncmp(shm_p,"exit",4) == 0)
    31. break;
    32. }
    33. //4、当不再使用时,解除映射关系
    34. shmdt(shm_p);
    35. //5、当没有进程再需要使用这块共享内存时,删除它
    36. shmctl(shmid, IPC_RMID, NULL);
    37. //6、关闭有名信号量。
    38. sem_close(nameSem);
    39. //7、删除有名信号量
    40. sem_unlink(SEM_NAME);
    41. return 0;
    42. }

    sem2 

    1. #define SEM_NAME "/semname1"
    2. int main()
    3. {
    4. //1、获取key
    5. key_t key = ftok(".",10);
    6. //2、根据key值 获取共享内存的ID号
    7. int shmid = shmget(key,1024,IPC_CREAT|0666);
    8. //3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
    9. char*shm_p = shmat(shmid,NULL,0);
    10. if(shm_p == (void*)-1)
    11. {
    12. perror("shmat error");
    13. return -1;
    14. }
    15. //4、创建并打开一个有名信号量
    16. sem_t *nameSem = sem_open(SEM_NAME,O_CREAT,0777,0);
    17. if(nameSem == SEM_FAILED )
    18. {
    19. printf("sem_open error\n");
    20. return -1;
    21. }
    22. //此时映射出来的shm_p 就是两个进程的共享内存
    23. while(1)
    24. {
    25. //有名信号量的P操作数据 -1
    26. sem_wait(nameSem);
    27. //从共享内存中读取数据
    28. printf("recv:%s\n",shm_p);
    29. //退出条件,这里要注意 应该使用strncmp 指定字节数
    30. if(strncmp(shm_p,"exit",4) == 0)
    31. break;
    32. }
    33. return 0;
    34. }

    3)无名信号量  -> 线程

    数据(比信号量要简单)
    sem_wait() //P操作
    sem_post() //V操作

    1. //定义一个无名信号量作用于整个进程
    2. sem_t g_sem;
    3. int g_val = 0;
    4. //线程例程,创建一条线程之后,去执行这个函数
    5. void* start_routine1(void *arg)
    6. {
    7. //数据P操作
    8. sem_wait(&g_sem);
    9. g_val = 100;
    10. sleep(2);//延时2秒,
    11. printf("start_routine1 g_val:%d\n",g_val);
    12. //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    13. sem_post(&g_sem);
    14. pthread_exit(NULL);
    15. }
    16. //线程例程,创建一条线程之后,去执行这个函数
    17. void* start_routine2(void *arg)
    18. {
    19. //数据P操作
    20. sem_wait(&g_sem);
    21. sleep(1);//延时1
    22. g_val = 200;
    23. printf("start_routine2 g_val:%d\n",g_val);
    24. //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    25. sem_post(&g_sem);
    26. pthread_exit(NULL);
    27. }
    28. //线程例程,创建一条线程之后,去执行这个函数
    29. void* start_routine3(void *arg)
    30. {
    31. //数据P操作
    32. sem_wait(&g_sem);
    33. //sleep(1);//延时1
    34. g_val = 250;
    35. printf("start_routine3 g_val:%d\n",g_val);
    36. //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    37. sem_post(&g_sem);
    38. pthread_exit(NULL);
    39. }
    40. int main()
    41. {
    42. int ret;
    43. //无名信号量的初始化
    44. sem_init(&g_sem,0,1);
    45. //创建一条子线程
    46. pthread_t thread1;
    47. ret = pthread_create(&thread1,NULL,start_routine1,NULL);
    48. if(ret != 0)
    49. {
    50. printf("pthread_create fail\n");
    51. return -1;
    52. }
    53. //创建一条子线程
    54. pthread_t thread2;
    55. ret = pthread_create(&thread2,NULL,start_routine2,NULL);
    56. if(ret != 0)
    57. {
    58. printf("pthread_create fail\n");
    59. return -1;
    60. }
    61. //创建一条子线程
    62. pthread_t thread3;
    63. ret = pthread_create(&thread3,NULL,start_routine3,NULL);
    64. if(ret != 0)
    65. {
    66. printf("pthread_create fail\n");
    67. return -1;
    68. }
    69. //数据P操作
    70. sem_wait(&g_sem);
    71. g_val = 1000;
    72. printf("main g_val:%d\n",g_val);
    73. //当这个线程不再使用这个共享资源的时候,无名信号量 进行 V操作
    74. sem_post(&g_sem);
    75. //等待子线程退出
    76. pthread_join(thread1,NULL);
    77. pthread_join(thread2,NULL);
    78. pthread_join(thread3,NULL);
    79. //销毁无名信号量
    80. sem_destroy(&g_sem);
    81. return 0;
    82. }

    6.线程之间的同步与互斥二

    1)互斥锁      -> 线程                    ----->重点

    动态初始化
    pthread_mutex_t m;
    int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
    静态初始化
    pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
    上锁与解锁
    int pthread_mutex_lock(pthread_mutex_t *mutex);//互斥锁上锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁锁

    1. //全局变量,用于多线程之间共同访问
    2. int g_val = 10;
    3. //方法一:1.定义一个互斥锁变量
    4. pthread_mutex_t mutex;
    5. //方法二:静态初始化一个互斥锁
    6. //pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    7. void *fun1(void *arg)
    8. {
    9. //设置子线程自己为分离属性不需要主线程join
    10. pthread_detach(pthread_self());
    11. //上锁(传的都是锁的地址)
    12. pthread_mutex_lock(&mutex);
    13. g_val = 20;
    14. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    15. //解锁
    16. pthread_mutex_unlock(&mutex);
    17. }
    18. void *fun2(void *arg)
    19. {
    20. //设置子线程自己为分离属性不需要主线程join
    21. pthread_detach(pthread_self());
    22. //上锁
    23. pthread_mutex_lock(&mutex);
    24. g_val = 200;
    25. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    26. //解锁
    27. pthread_mutex_unlock(&mutex);
    28. }
    29. int main(int argc,char **argv)
    30. {
    31. //2.初始化互斥锁(注意传的是锁的地址)
    32. //pthread_mutex_init(&mutex,NULL);
    33. int ret;
    34. pthread_t tid1,tid2;
    35. //创建线程fun1
    36. ret = pthread_create(&tid1,NULL,fun1,NULL);
    37. if(ret != 0)
    38. {
    39. printf("pthread_create fail\n");
    40. return -1;
    41. }
    42. //创建线程fun2
    43. ret = pthread_create(&tid2,NULL,fun2,NULL);
    44. if(ret != 0)
    45. {
    46. printf("pthread_create fail\n");
    47. return -1;
    48. }
    49. //上锁
    50. pthread_mutex_lock(&mutex);
    51. g_val = 250;
    52. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    53. //解锁
    54. pthread_mutex_unlock(&mutex);
    55. sleep(2);
    56. //销毁锁
    57. pthread_mutex_destroy(&mutex);
    58. return 0;
    59. }

    2)读写锁      -> 线程                    ---->重点

    读锁就是一把共享锁
    写锁就是一把互斥锁
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读锁上锁
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写锁上锁
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//读写解锁
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁锁

    1. //全局变量,用于多线程之间共同访问
    2. int g_val = 10;
    3. //1.定义一个读写锁变量
    4. pthread_rwlock_t rwlock;
    5. void *fun1(void *arg) //写操作
    6. {
    7. //上写锁
    8. pthread_rwlock_wrlock(&rwlock);
    9. int cnt=3;
    10. while(cnt--)
    11. {
    12. g_val = 20;
    13. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    14. }
    15. //解锁
    16. pthread_rwlock_unlock(&rwlock);
    17. }
    18. void *fun2(void *arg)//写操作
    19. {
    20. //上写锁
    21. pthread_rwlock_wrlock(&rwlock);
    22. int cnt=3;
    23. while(cnt--)
    24. {
    25. g_val = 200;
    26. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    27. }
    28. //解锁
    29. pthread_rwlock_unlock(&rwlock);
    30. }
    31. void *fun3(void *arg)//读操作
    32. {
    33. //上读锁
    34. pthread_rwlock_rdlock(&rwlock);
    35. int cnt=3;
    36. while(cnt--)
    37. {
    38. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    39. sleep(1);
    40. }
    41. //解锁
    42. pthread_rwlock_unlock(&rwlock);
    43. }
    44. void *fun4(void *arg)//读操作
    45. {
    46. //上读锁
    47. pthread_rwlock_rdlock(&rwlock);
    48. int cnt=3;
    49. while(cnt--)
    50. {
    51. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    52. sleep(1);
    53. }
    54. //解锁
    55. pthread_rwlock_unlock(&rwlock);
    56. }
    57. int main(int argc,char **argv)
    58. {
    59. //2.读写锁的初始化
    60. pthread_rwlock_init(&rwlock,NULL);
    61. int ret;
    62. pthread_t tid1,tid2,tid3,tid4;
    63. //创建线程fun1
    64. ret = pthread_create(&tid1,NULL,fun1,NULL);
    65. if(ret != 0)
    66. {
    67. printf("pthread_create fail\n");
    68. return -1;
    69. }
    70. //创建线程fun2
    71. ret = pthread_create(&tid2,NULL,fun2,NULL);
    72. if(ret != 0)
    73. {
    74. printf("pthread_create fail\n");
    75. return -1;
    76. }
    77. //创建线程fun3
    78. ret = pthread_create(&tid3,NULL,fun3,NULL);
    79. if(ret != 0)
    80. {
    81. printf("pthread_create fail\n");
    82. return -1;
    83. }
    84. //创建线程fun4
    85. ret = pthread_create(&tid4,NULL,fun4,NULL);
    86. if(ret != 0)
    87. {
    88. printf("pthread_create fail\n");
    89. return -1;
    90. }
    91. //上写锁
    92. pthread_rwlock_wrlock(&rwlock);
    93. g_val = 250;
    94. printf("[%s]g_val = %d\n",__FUNCTION__,g_val);
    95. //解锁
    96. pthread_rwlock_unlock(&rwlock);
    97. pthread_join(tid1,NULL);
    98. pthread_join(tid2,NULL);
    99. pthread_join(tid3,NULL);
    100. pthread_join(tid4,NULL);
    101. //销毁读写锁
    102. pthread_rwlock_destroy(&rwlock);
    103. return 0;
    104. }

    3)条件变量    -> 线程(互斥锁+条件变量一起使用)  ---->重点

    //初始化互斥锁
    //初始化条件变量

    //自动解锁 ,并且阻塞等待  
    pthread_cond_wait(&cond,&mutex);

    1. //全局变量-临界资源(银行卡余额)
    2. int g_money = 2000;
    3. //定义一个互斥锁变量
    4. pthread_mutex_t mutex;
    5. //定义一个条件变量并且静态初始化
    6. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    7. void *start_routine(void*arg)
    8. {
    9. printf("[%lu]子线程 start\n",pthread_self());
    10. pthread_mutex_lock(&mutex);//加锁 力度要小
    11. //条件不满足的时候进入 条件变量中等待
    12. while(g_money<1000)
    13. {
    14. printf("没钱了,进去条件变量中等待父母打钱 并且通知.....\n");
    15. //自动解锁 ,并且阻塞等待
    16. pthread_cond_wait(&cond,&mutex);
    17. printf("父母打钱过来了,已经通知我了,此时余额:%d\n",g_money);
    18. }
    19. //走到这里,说明有钱
    20. g_money -=1000;
    21. printf("[%lu]子线程 拿到钱了,此时银行卡余额:%d\n",pthread_self(),g_money);
    22. pthread_mutex_unlock(&mutex); //解锁
    23. printf("[%lu]子线程 end\n",pthread_self());
    24. //拿钱走人
    25. pthread_exit(NULL);
    26. }
    27. int main()
    28. {
    29. //初始化互斥锁
    30. pthread_mutex_init(&mutex,NULL);
    31. pthread_t thread1; //
    32. pthread_create(&thread1,NULL,start_routine, NULL);
    33. pthread_t thread2; //
    34. pthread_create(&thread2,NULL,start_routine, NULL);
    35. pthread_t thread3; //
    36. pthread_create(&thread3,NULL,start_routine, NULL);
    37. pthread_t thread4; //
    38. pthread_create(&thread4,NULL,start_routine, NULL);
    39. int cnt=5;
    40. while(cnt--)
    41. {
    42. sleep(1);
    43. printf("主线程(父母) 即将准备打钱....%d\n",cnt);
    44. }
    45. //主线程(父母) 打钱
    46. pthread_mutex_lock(&mutex);//加锁
    47. g_money +=1000;
    48. pthread_mutex_unlock(&mutex); //解锁
    49. pthread_cond_broadcast(&cond);//广播: 唤醒所有在条件变量中等待的线程
    50. cnt=5;
    51. while(cnt--)
    52. {
    53. sleep(1);
    54. printf("主线程(父母) 即将准备打钱....%d\n",cnt);
    55. }
    56. pthread_mutex_lock(&mutex);//加锁
    57. g_money +=1000;
    58. pthread_mutex_unlock(&mutex); //解锁
    59. pthread_cond_signal(&cond);//单播: 随机唤醒一个在条件变量中等待的线程
    60. pthread_join(thread1, NULL);
    61. pthread_join(thread2, NULL);
    62. pthread_join(thread3, NULL);
    63. pthread_join(thread4, NULL);
    64. pthread_mutex_destroy(&mutex);
    65. pthread_cond_destroy(&cond);
    66. return 0;
    67. }


     

  • 相关阅读:
    【SLAM】SVO2.0编译运行和论文代码解读
    学会这几个软件,轻松实现图片翻译文字
    UG\NX二次开发 获取调色板CDF文件的内容
    JavaScript基础——练习巩固题目(1)
    SpringBoot【集成 jasypt】实现配置信息自定义加解密(自定义的属性探测和密码解析器)
    Win10怎么重启资源管理器?重启资源管理器快捷键是什么
    SELinux零知识学习十六、SELinux策略语言之类型强制(1)
    Web爬虫--fofa-资产信息搜集
    降维(Dimensionality Reduction)
    使用记账软件记录生活收支明细,如何防止收支不被他人修改
  • 原文地址:https://blog.csdn.net/weixin_48102054/article/details/127445143