• Linux20 -- 线程安全、保证线程安全的示例代码


    一、线程安全

    线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的。那么就说这些线程是安全的。

    线程安全:多线程程序,无论调度顺序咋样,都可以得到正确一致的结果。安全–正确性。
    同步,线程安全的函数/可重入函数。

    要保证线程安全需要做到:
    1) 对线程同步,保证同一时刻只有一个线程访问临界资源。
    2)在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。

    二、保证线程安全的示例代码

    1、strtok函数的线程调用示例,不保证线程安全:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    void* fun(void *arg)
    {
        char buff[]={"a b c d e f g h j"};
        char *s = strtok(buff," ");
        while(s != NULL)
        {
            printf("fun s = %s\n",s);
            sleep(1);
            s = strtok(NULL," ");
        }
    }
    
    int main()
    {
        pthread_t id;
        pthread_create(&id,NULL,fun,NULL);
    
        char arr[]="1 2 3 4 5 6 7 8 9";
        char *s = strtok(arr," ");
        while(s != NULL)
        {
            printf("main s = %s\n",s);
            sleep(1);
            s = strtok(NULL," ");
        }
    
        pthread_join(id,NULL);
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    运行结果:
    在这里插入图片描述

    2、strtok_r函数的线程调用

    #include 
    #include 
    #include 
    #include 
    #include 
    
    void* fun(void *arg)
    {
        char buff[]={"a b c d e f g h j"};
        char * ptr = NULL;
        char *s = strtok_r(buff," ",&ptr);
        while(s != NULL)
        {
            printf("fun s = %s\n",s);
            sleep(1);
            s = strtok_r(NULL," ",&ptr);
        }
    }
    
    int main()
    {
        pthread_t id;
        pthread_create(&id,NULL,fun,NULL);
    
        char arr[]="1 2 3 4 5 6 7 8 9";
        char *ptr = NULL;
        char *s = strtok_r(arr," ",&ptr);
        while(s != NULL)
        {
            printf("main s = %s\n",s);
            sleep(1);
            s = strtok_r(NULL," ",&ptr);
        }
    
        pthread_join(id,NULL);
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    运行结果:

    在这里插入图片描述

    三、线程与 fork

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

    ps -ef
    ps -eLf --查看线程id

    #include 
    #include 
    #include 
    #include 
    #include 
    
    void* fun(void *arg)
    {
        for(int i = 0;i < 5;i++)
        {
            printf("fun run , 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 run , pid = %d\n",getpid());
            sleep(1);
        }
    
        pthread_join(id,NULL);
        exit(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    运行结果:

    在这里插入图片描述

    原理:

    Linux实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。

    2、父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁?

    fork会将锁复制,但为保证安全,会将锁默认锁上。
    为防止出现错误。父子进程在使用锁的时候,可以先对其进行解锁,再使用锁。

    解决方法:pthread_atfork()

    test3.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    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_t id;
        pthread_mutex_init(&mutex,NULL);//对锁进行初始化
        pthread_create(&id,NULL,fun,NULL);
        
        sleep(1);//等待线程运行,等其进行加锁
        pid_t pid = fork(); 
        if(pid == -1)
        {
            exit(0);
        }
    
        if(pid == 0)
        {
            printf("子进程将要加锁\n");
            pthread_mutex_lock(&mutex);
            printf("子进程加锁成功\n");
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            wait(NULL);//子进程未结束,会阻塞
            printf("main over\n");
        }
    
        pthread_join(id,NULL);
        exit(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    test4.c //使用pthread_atfork

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    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");
    }
    
    void at_lock()
    {
        pthread_mutex_lock(&mutex);
    }
    
    void at_unlock()
    {
        pthread_mutex_unlock(&mutex);
    }
    
    int main()
    {
        pthread_t id;
        pthread_atfork(at_lock,at_unlock,at_unlock);
        pthread_mutex_init(&mutex,NULL);//对锁进行初始化
        pthread_create(&id,NULL,fun,NULL);
        
        sleep(1);//等待线程运行,等其进行加锁
        pid_t pid = fork(); 
        if(pid == -1)
        {
            exit(0);
        }
    
        if(pid == 0)
        {
            printf("子进程将要加锁\n");
            pthread_mutex_lock(&mutex);
            printf("子进程加锁成功\n");
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            wait(NULL);//子进程未结束,会阻塞
            printf("main over\n");
        }
    
        pthread_join(id,NULL);
        exit(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    运行结果:

    在这里插入图片描述

  • 相关阅读:
    CSS盒子模型、列表样式
    (WRF-UCM)高精度城市化气象动力模拟技术
    通俗易懂地说一下 nft
    机器人过程自动化(RPA)入门 1. 什么是机器人过程自动化?
    RabbitMQ 学习(五)-- 死信队列
    《QT从基础到进阶·二十六》绘制多个图形项(QGraphicsRectItem,QGraphicsLineItem,QGraphicsPolygonItem)
    vue3 +element-plus中避免一打开表单的下拉选择的change事件自动校验问题
    HTML+CSS:动态搜索框
    python 日常处理数据中使用到的零碎知识点(一)
    反汇编ARM程序的技术靠谱吗?——揭秘ARM架构二进制程序的反汇编技术现状
  • 原文地址:https://blog.csdn.net/kyrie_sakura/article/details/127970603