• 2022-07-01 多线程互斥锁的一个案例


    前言

    最近回答了一个C语言多线程的小问题.

    大概有些初涉多线程编程的学习者,在未学到 “锁” 这个概念的时候就已经开始写多线程代码。

    但有个问题,要先明白,对于没有任何规划的多线程,很容易得到与预想相别万里的结果。

    因为逻辑变了,单线程是顺序逻辑,多线程是乱序逻辑,需要人为干涉规划执行顺序。

    举例

    下面是问题的叙述,在无锁情况(并非可无锁并发的数据结构)导致的乱序:

    C语言多线程操作多函数冲突

    问题遇到的现象和发生背景

    通过移动光标的方式,用两个线程执行两个不同函数分别在某列输出不同的数字。由于线程并发,输出的结果不对

    问题代码可以参考链接:C语言多线程操作多函数 冲突

    程序调用两个线程,运行两个函数:fun1,fun2,这两个函数分别调用 gotoxy 函数,定位光标,然后打印字符。

    单线程顺序执行没有问题,多线程有很大问题。

    两个线程会乱序的定位光标,乱序打印,导致这样一种情况:A挪光标,还没打印,B打印了,随后A才打印,或者相反,总之不是想要的结果。

    为了梳理程序流程,我们需要加锁,让光标移动和打印原子化。

    OK,我们在光标移动前和打印后加锁。

    现在的问题是,可以有很多地方加锁,具体放哪里?

    答案是放在刚刚要定位光标之前,刚刚打印完毕。

    这看似是废话,其实还有有一定道理的。

    一旦一个进程持有锁,另外一个进程就要等,我可以粗糙的放在 gotoxy 函数之前和 printf 函数之后,没有问题,但是我们想想,gotoxy 内部的变量初始化及句柄获取要不要占时间,它们要占时间,但是和移动光标没有关系,完全可以先准备出来,等持有锁的线程释放后立刻定位光标。

    一句话,先把能干的都干了,只等时机到来。多么哲学。

    当然这只是一个最简单的用例,基本没有死锁的风险,也没有恶性数据竞争,属于入门的入门。

    以下代码:

    #include <pthread.h>
    #include <stdio.h>
    #include <windows.h>
    
    pthread_mutex_t m;
    
    void gotoxy(short x, short y)
    {
        COORD pos;
        HANDLE hOutput;
        pos.X = x;
        pos.Y = y;
        hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
        pthread_mutex_lock(&m);
        SetConsoleCursorPosition(hOutput, pos);
    }
    
    void *fun1(void *unused)
    {
        short t = 0;
        while (t < 20)
        {
            gotoxy(10, t++);
            printf("11");
            pthread_mutex_unlock(&m);
        }
        pthread_exit(NULL);
        return NULL;
    }
    
    void *fun2(void *unused)
    {
        short t = 0;
        while (t < 20)
        {
            gotoxy(1, t++);
            printf("22");
            pthread_mutex_unlock(&m);
        }
        pthread_exit(NULL);
        return NULL;
    }
    
    int main()
    {
        pthread_mutex_init(&m, NULL);
    
        pthread_t pthread1;
        pthread_t pthread2;
    
        pthread_create(&pthread1, NULL, &fun1, NULL);
        pthread_create(&pthread2, NULL, &fun2, NULL);
    
        pthread_join(pthread1, NULL);
        pthread_join(pthread2, NULL);
    
        pthread_mutex_destroy(&m);
    
        return 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
  • 相关阅读:
    LeetCode 202. 快乐数
    Docker——开源的应用容器的引擎
    性能测试环境部署
    计算机毕业设计Java电影周边产品查找系统(源码+系统+mysql数据库+lw文档)
    【因果推断python】41_异质干预效应1
    庆金秋贺国庆 | 实验必备「取样一本通•植物篇」包邮免费送!
    java springboot在当前测试类中添加临时属性 不影响application和其他范围
    数字孪生技术:新零售的未来之路
    WIFI简介 以及SDIO总线介绍
    设置linux的时间
  • 原文地址:https://blog.csdn.net/m0_54206076/article/details/125565550