Linux Day16 多线程的一些常见问题

2023-09-18 23:40:52

目录

一、多线程+fork()

问题一:多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?

1.1.1 不使用fork前,让线程函数和主程序打印其进程号

结果:

结论:

1.1.2 在主程序中加入fork

结果:

结论:

1.1.3 线程函数加入fork()

 结果:

结论:

综上所述:多线程程序fork后,子进程只启用一条执行路径,就是fork所在的执行路径。

 问题二: 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁

代码

结果

分析

解决

结果


一、多线程+fork()

问题一:多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?

1.1.1 不使用fork前,让线程函数和主程序打印其进程号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *fun(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        printf("fun pid=%d\n", getpid());
        sleep(1);
    }
}
int main()
{
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    for (int i = 0; i < 5; i++)
    {
        printf("main pid=%d\n", getpid());
        sleep(1);
    }
    pthread_join(id,NULL);
    exit(0);
}

结果:

结论:

不难发现,线程函数和主函数的进程号是一样的

1.1.2 在主程序中加入fork

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *fun(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        printf("fun pid=%d\n", getpid());
        sleep(1);
    }
}
int main()
{
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    fork();
    for (int i = 0; i < 5; i++)
    {
        printf("main pid=%d\n", getpid());
        sleep(1);
    }
    pthread_join(id,NULL);
    exit(0);
}

结果:

结论:

不难发现父进程中打印主线程和线程函数id=3519,而子进程执行了主线程id=3521,子进程只有一条执行路径。

1.1.3 线程函数加入fork()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *fun(void *arg)
{
    fork();
    for (int i = 0; i < 5; i++)
    {
        printf("fun pid=%d\n", getpid());
        sleep(1);
    }
}
int main()
{
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    
    for (int i = 0; i < 5; i++)
    {
        printf("main pid=%d\n", getpid());
        sleep(1);
    }
    pthread_join(id,NULL);
    exit(0);
}

 结果:

结论:

不难发现父进程中打印主线程和线程函数id=3551,而子进程执行了线程函数id=3553,子进程只有一条执行路径。

综上所述:多线程程序fork后,子进程只启用一条执行路径,就是fork所在的执行路径。

 问题二: 父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁

多线程中fork以后产生子进程,子进程共享父进程的内容,但是会不会共享锁或者信号量呢,下面我们举个栗子。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include<sys/wait.h>
pthread_mutex_t mutex;
void*fun(void*arg)
{
    pthread_mutex_lock(&mutex);
    printf("fun lock\n");
    sleep(5);
    pthread_mutex_unlock(&mutex);
    printf("fun unlock\n");
}
int main()
{
    pthread_mutex_init(&mutex,NULL);
    pthread_t id;
    pthread_create(&id,NULL,fun,NULL);
    sleep(1);
    pid_t pid =fork();
    if(pid==-1)
    {
        exit(0);
    }
    if(pid==0)
    {
        printf("child 准备 lock\n");
        pthread_mutex_lock(&mutex);
        printf("child枷锁成功\n");
        pthread_mutex_unlock(&mutex);
        exit(0);
    }
    wait(NULL);
    printf("main over\n");
    exit(0);
}

结果

分析

代码从主程序开始执行,执行到线程函数时,创建线程,进入fun()后,加锁,打印“fun lock”,随后睡眠5秒,我们知道多线程是有并发这个特性,这个时候就会继续主函数,进行fork,这个时候我们发现打印了"child 准备lock",注意此时我们线程函数中的锁还没有解,就有了一个新的锁,说明父进程和子进程的锁不是共用一个锁,此后5秒睡眠时间结束,这时继续执行多线程函数,解锁打印“fun unlock”,但是我们发现一件事:此函数阻塞了。

接下来就是这个问题的核心之所在。

fork()会将父进程的内容给子进程复制一份,同时也会把锁的状态给子进程,如在fork前锁还没有上,那么复制给子进程的锁就是没有上的。所以这里我们在fork前父进程就已经上了锁,传递给子进程后,子进程刚开始的锁就是上锁状态,所以就不会执行上锁状态,因为没有解锁。

解决

int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

该函数通过3个不同阶段的回调函数来处理互斥锁状态。参数如下:
prepare:将在fork调用创建出子进程之前被执行,它可以给父进程中的互斥锁对象明明确确上锁。这个函数是在父进程的上下文中执行的,正常使用时,我们应该在此回调函数调用 pthread_mutex_lock 来给互斥锁明明确确加锁,这个时候如果父进程中的某个线程已经调用pthread_mutex_lock给互斥锁加上了锁,则在此回调中调用 pthread_mutex_lock 将迫使父进程中调用fork的线程处于阻塞状态,直到prepare能给互斥锁对象加锁为止。

parent: 是在fork调用创建出子进程之后,而fork返回之前执行,在父进程上下文中被执行。它的作用是释放所有在prepare函数中被明明确确锁住的互斥锁。
child: 是在fork返回之前,在子进程上下文中被执行。和parent处理函数一样,child函数也是用于释放所有在prepare函数中被明明确确锁住的互斥锁。

函数成功返回0, 错误返回错误码。
 

pthread_mutex_t mutex;

void fork_lock(void)
{
    pthread_mutex_lock(&mutex);
}

void fork_unlock(void)
{
    pthread_mutex_unlock(&mutex);
}

void * fun(void* arg)
{
    pthread_mutex_lock(&mutex);
    printf("fun lock\n");
    sleep(5);
    pthread_mutex_unlock(&mutex);
    printf("fun unlock\n");

}
int main()
{

    pthread_mutex_init(&mutex,NULL);
    pthread_t id;
    pthread_atfork(fork_lock,fork_unlock,fork_unlock);
    pthread_create(&id,NULL,fun,NULL);
    sleep(1);

    pid_t pid = fork();
    if ( pid == -1 )
    {
        exit(1);
    }

    if ( pid == 0 )
    {
        printf("child 准备lock\n");
        pthread_mutex_lock(&mutex);//阻塞
        printf("child加锁成功\n");
        pthread_mutex_unlock(&mutex);

        exit(0);
    }

    wait(NULL);
    printf("main exit\n");
    exit(0);

}

结果

到这里线程的同步就更新这么多啦,明天更新生产者消费者模型。

更多推荐

mysql自动删除过期的binlog

一、binlog_expire_logs_seconds配置项mysql8.0使用配置项binlog_expire_logs_seconds设置binlog过期时间,单位为秒。mysql旧版本使用配置项expire_logs_days设置binlog过期时间,单位为天,不方便测试。在8.0使用expire_logs_d

OPTEE Benchmark框架

安全之安全(security²)博客目录导读OPTEE调试技术汇总目录一、序言二、Benchmark框架三、Benchmark实现细节1、设计概况2、时间戳源3、调用时序图4、添加自定义时间戳5、构建并运行Benchmark6、限制和进一步措施一、序言本节中描述的特性依赖于上游不可用的Linuxkernelpatch,

conda手动下载虚拟环境中的包

一.下载导入tar安装包1.准备安装包安装包可从官网下载,或直接拷贝已有虚拟环境中的包【补充】关于虚拟环境中包的存储路径打开cmd,输入condaconfig--show查看pkgs_dirs属性的值C:\Users\XXX>condaconfig--showpkgs_dirs:-D:\program\anaconda

PostgreSQL 事务&并发&锁

文章目录PostgreSQL事务大家都知道的ACID事务的基本使用保存点PostgreSQL并发并发问题MVCCPostgreSQL锁机制表锁行锁总结PostgreSQL事务大家都知道的ACID在日常操作中,对于一组相关操作,通常要求要么都成功,要么都失败。在关系型数据库中,称这一组操作为事务。为了保证整体事务的安全性

软件设计模式系列之十二——外观模式

在软件设计中,经常会遇到需要与复杂子系统进行交互的情况。为了简化客户端与子系统之间的交互,提高系统的可维护性和可用性,外观模式应运而生。外观模式(FacadePattern)是一种结构型设计模式,它提供一个统一的界面,用于访问系统中的一组相关接口,从而隐藏了系统的复杂性。在本文中,我们将深入探讨外观模式,包括其定义、举

员工犯错,就应该受惩罚吗?

先说结论,惩罚肯定是不能有的。我们应该指导员工减少犯错的概率。在项目开发过程中,总会遇到项目成员犯错的场景。根据所犯错误是否具有共性特征,我把问题归为二类,不同类型的错误,采取不同的应对措施。第一类:有共性的问题。第二类:没有共性的问题。共性的定义:一个人多次或者多个人都犯过的错误。第一类:有共性的问题。对于有共性的问

vue3 - 使用reactive定义响应式数据进行赋值时,视图没有改变,值已经改变的解决方案

问题:在Vue3.0中我们使用reactive()定义的响应式数据的时候,当是一个数组或对象时,我们直接进行赋值,发现数据已经修改成功,但是页⾯并没有自动渲染成最新的数据;这是为什么呢?就如同官网所说的reactive存在一些局限性:(官方的描述)原因:原因就是reactive函数会返回一个Proxy包装的对象,所以当

网络安全深入学习第六课——热门框架漏洞(RCE— Weblogic反序列化漏洞)

文章目录一、Weblogic介绍二、Weblogic反序列化漏洞历史三、Weblogic框架特征1、404界面2、登录界面四、weblogic常用弱口令账号密码五、Weblogic漏洞介绍六、Weblogic漏洞手工复现1、获取账号密码,这是一个任意文件读取的漏洞1)读取SerializedSystemIni.dat文

面向对象的分析与设计(精品课程)第二章作业

面向对象的分析与设计(精品课程)第二章作业一.单选题(共4题,30.4分)二.多选题(共1题,7.6分)三.填空题(共5题,38分)四.简答题(共3题,24分)一.单选题(共4题,30.4分)(单选题)UML中的事物包括结构事物、分组事物、注释事物和()。A实体事物B边界事物C控制事物D动作事物(单选题)UML中的4种

Linux设备树OF操作函数

OF操作函数我们知道Linux内核使用设备树的形式去描述芯片硬件设备节点的各种属性,设备树的树形结构可以层次化的组织这些节点属性。设备树源码属于脚本格式的文件,Linux内核无法直接使用脚本格式,所以最终使用时需要将设备树源码编译为二进制的“.dtb”格式,最终Uboot将".dtb"格式设备树传递给Linux内核使用

pdf文件可以压缩大小吗?pdf压缩方法分享

在日常生活和工作中,我们经常需要处理大量的PDF文件。有时候,一个PDF文件的大小可能超过了几十MB,甚至无法通过电子邮件发送。那么,如何有效地压缩PDF文件大小呢?本文将为你介绍三个简单易行的方法,帮助你轻松搞定PDF文件压缩大小的问题。首先,我们需要了解PDF文件的相关概念,PDF是一种可移植文档格式,可以跨平台、

热文推荐