Linux——线程同步与生产者消费者模型

news2025/9/21 19:45:35

目录

前言

一、线程同步

二、生产者消费者模型

三、条件变量

1.理解条件变量

2.条件变量接口

2.1 条件变量初始化与销毁

2.2 条件变量等待

 2.3 条件变量唤醒等待

2.4 条件变量接口运用

2.5 条件变量进行抢票 

3.条件变量的细节 

四、基于BlockingQueue的生产者消费者模型

1.单线程版 

2.使用LockGuard 

3.多线程版 


前言

之前我们学习了线程互斥,知道了系统为保护临界资源,需要让线程互斥,一个个的访问资源,同时我们也发现线程饥饿的问题,就是某一个线程一直持有锁,释放锁,再持有锁,释放锁,其他线程根本就竞争不过他,因此又提出了线程同步,让线程有序的进行申请锁资源

一、线程同步

我们讲个小故事

  1. 之前,我们想象中的同步就是夫妻两人你看你的斗破苍穹,我玩我的英雄联盟。我们两个一起进行且互不干扰。
  2. 但是今天,我们都要向存钱罐里拿钱,你为了去买更好的手机看小说,我为了去买更好的电脑打游戏。
  3. 我们也不能一起将手往存钱罐里面摸,因为存钱罐口就那么大,因此就得互斥的去拿钱,也就是我拿的时候你不能拿,你拿的时候我不能拿。
  4. 但是你也不能太霸道的,比如说你一直去拿钱,刚拿完钱出来,又将手伸过去拿,导致钱都被你拿光了,我一直无法从里面拿钱,那我就饥饿了。
  5. 这样长此以往肯定不行,那么现在规则修改了,从存钱灌里面拿钱之后,不能再立刻申请,而应该在后面排队,这样每个人都可以拿到钱干自己想干的事情。

在这个故事中

  • 夫妻二人就是两个线程
  • 钱是临界资源
  • 存钱罐口就是一把锁
  • 同一时间只能一个人拿钱叫做线程互斥
  • 按顺序拿钱就是线程同步

线程同步是在临界资源使用安全的前提下,让多线程执行具有一定的顺序性

互斥能保证资源的安全,同步能较为充分高效的使用资源

二、生产者消费者模型

在我们生活中,超市是典型的生产者消费者模型。生产者生成商品放入超市,消费者进入超市消费商品。

在计算机生产者消费者模型中,生产者是线程,消费者也是线程。生产线程生成数据,消费线程消费(处理)数据。而超市本质上就是内存空间

在这个过程中,我们必须保证生产消费过程是安全的! 

  • 不可能说你想在超市的最好的地方放东西,我也想在这里放东西,这样就会发生数据覆盖的现象,因此生产者之间是需要互斥的
  • 而超市的资源(数据)是很珍贵的,如果两人一起访问并做处理,可能会发生数据紊乱或者浪费消费者资源的事情。因此消费者之间也是互斥的
  • 生产者和消费者之间,必须也是互斥的,因为你放入完毕之后我才能来拿,如果你刚放入一点,比如想放入“hello world”,只放入了“hello”我就来拿走,也会发生问题。
  • 同时生产者和消费者之间,还得是同步的。如果仅仅是互斥,那么可以连续一个月生产者都在往里面放资源,消费者没机会来消费,那就效率太低下了。同步之后,生产者发现我生产的东西比较多了,就让消费者来拿,消费者看见超市里面没什么东西了,就让生产者生产。这样就可以保证生产者消费者模型更好的运转。

小总结

生产者之间互斥

消费者之间互斥

生产者与消费者之间互斥+同步 

三、条件变量

1.理解条件变量

我们说了这么多的同步与生产者消费者模型,那如何实现线程同步呢?pthread库给我们提供了条件变量。

我们讲个小故事来理解条件变量

  • 有一个拿苹果游戏,一个人往箱子里放苹果,另一个人蒙着眼睛从箱子里拿苹果,箱子只有一个口,就是要么只允许放,要么只允许拿。
  • 拿苹果的人需要尽快拿到苹果,但又不清楚放苹果的人什么时候会放入苹果,于是他疯狂的从箱子里面拿,刚摸了一下发现没苹果,过了几秒不放心,感觉可能放苹果的人已经放入了苹果,于是又想去拿苹果。
  • 就这样,拿苹果的人一直在申请拿苹果,人家放苹果的人就算到来了也来不及放,因为你一直拿,占用着锁。
  • 于是乎,现在你们想了一个办法,就是你先不要来拿,你等我发消息,当我放入的时候会给你发送消息,你现在再来拿苹果。
  • 这个消息就是条件变量!!!!

如果拿苹果的人很多,他们也需要排队,那么条件变量中肯定要维护一个线程队列,同时还要有个标志位判断条件是否就绪。

2.条件变量接口

2.1 条件变量初始化与销毁

如下是条件变量的初始化和销毁接口,特别类似于互斥锁。

定义全局:PTHREAD_COND_INITAILIZER  直接初始化即可

定义局部:pthread_cond_init()

  • 参数cond:pthread_cond_t cond创建条件变量,将&cond传入即可。
  • 参数attr:设置条件变量属性,默认传NULL;

销毁:pthread_cond_destroy()

  • 参数:&cond

2.2 条件变量等待

条件变量等待接口如下,(等待的是资源就绪)

pthread_cond_wait()

  • 参数cond:需要等待的条件变量
  • 参数mutex:需要的锁
  • 返回值:成功返回0,失败返回错误原因。 

 2.3 条件变量唤醒等待

条件变量唤醒等待接口如下

pthread_cond_signal()作用是使指定的条件变量成立,并且唤醒一个线程

pthread_cond_broadcast()作用是使指定的条件变量成立,并且唤醒所有线程

他们两个的参数都是 &cond

2.4 条件变量接口运用

如下是互斥代码,通过打印观察运行情况

#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>

using namespace std;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *threadRoutine(void *args)
{
    string name = static_cast<const char*> (args);
    while (true)
    {
        sleep(1);
        pthread_mutex_lock(&mutex);
        cout<<"I am a new thread : "<<name<<endl;
        pthread_mutex_unlock(&mutex); 
    }
    
}

int main()
{
    
    pthread_t t1,t2,t3;
    pthread_create(&t1,nullptr,threadRoutine,(void*)"thread_1");
    pthread_create(&t2,nullptr,threadRoutine,(void*)"thread_2");
    pthread_create(&t3,nullptr,threadRoutine,(void*)"thread_3");

    pthread_join(t1,nullptr);
    pthread_join(t2,nullptr);
    pthread_join(t3,nullptr);
}

运行发现现在仅仅是互斥,并没有同步,因为顺序不一致 

现在我们添加上条件变量代码,让线程开始等待,于是所有运行该函数的线程都会停留在wait处进行等待。而我们主线程,过了5秒开始唤醒等待,看看线程运行情况。

现在就是有一定的顺序性了,因为被唤醒的线程处理完毕后又会去进行等待,此时是添加在了等待队列的末尾。由于每次唤醒都要sleep(1),因此每一行都需要等一秒才能打印

如果我们将唤醒切换成broadcast,那么就是每一秒打印三次,因为有三个进程都被唤醒了。依然是有序的

2.5 条件变量进行抢票 

 现在我们让线程都去抢票,没票的时候就进行等待

那么现在没票之后,线程就不会疯狂的申请锁,释放锁,而是进行等待,等待后续还有票,你就给我发送唤醒,我再开始抢票。 

现在我们每隔5秒放一百张票,放完票就进行唤醒,让线程再去抢票 

 现在没票线程就等待,有票线程就开始抢,能够很好的控制线程抢票了。

3.条件变量的细节 

刚才我们看到,单纯的互斥,能保证数据安全,但不一定合理或者高效。因为如果不进行条件变量等待,那么线程会一直申请锁,发现没有票,再释放锁,while循坏又会来申请锁。可能主线程想要申请锁进行放票,都没有机会。

现在让线程再pthread_cond_wait这里进行等待,没有问题,但是你等待的时候必须要释放锁,因为如果你不释放,其他线程也就进不来。这也是为何pthread_cond_wait不仅仅要传环境变量,还要传mutux。

同时,线程被唤醒的时候,是在临界区中被唤醒的,当线程被唤醒,线程在pthread_cond_wait返回时,需要重新申请锁

四、基于BlockingQueue的生产者消费者模型

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。

其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出。(也就是队列为空不能消费者不能消费,队列为满生产者不能生产

我们可以通过控制,来让生产和消费保持同步

1.单线程版 

我们利用C++提供的queue来封装阻塞队列,生产者往队列里面入数据,并发送唤醒让消费者消费,消费者往队列里面出数据,并发送唤醒让生产者生产数据。

BlockQueue.hpp代码如下

#pragma once

#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;

const int defaultcap = 5;

template<class T>
class BlockQueue
{
public:
    BlockQueue(int capacity = defaultcap)
        :_capacity(capacity)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_p_cond,nullptr);
        pthread_cond_init(&_c_cond,nullptr);
    }

    bool IsFull()
    {
        return _q.size()==_capacity;
    }

    bool IsEmpty()
    {
        return _q.size()==0;
    }

    void Push(const T& in)//生产者
    {
        pthread_mutex_lock(&_mutex);
        while(IsFull())
        {
            //阻塞等待
            pthread_cond_wait(&_p_cond,&_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_c_cond);
        pthread_mutex_unlock(&_mutex);
    }

    void Pop(T* out)    //消费者
    {
        pthread_mutex_lock(&_mutex);
        while(IsEmpty())
        {
            //阻塞等待
            pthread_cond_wait(&_c_cond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_p_cond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }
private:
    queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond;//生产者条件变量
    pthread_cond_t _c_cond;//消费者条件变量
};

main.cc如下

#include "BlockQueue.hpp"
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
void *consumer(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while (true)
    {
        int data = 0;
        bq->Pop(&data);
        cout<<"consumer data: "<<data<<endl;
    }
    return nullptr;
}

void *productor(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while (true)
    {
        int data = rand()%10+1;
        bq->Push(data);
        cout<<"productor data:"<<data<<endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    srand(time(nullptr));
    BlockQueue<int> *bq = new BlockQueue<int>();

    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void *)bq);
    pthread_create(&p, nullptr, productor, (void *)bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
}

运行就会发现,你生产一个,我就消费一个,不仅仅是单纯的互斥,而是进行了同步。不会再有线程饥饿的问题。

2.使用LockGuard 

 我们使用LockGuard来创建锁,让他创建时直接加锁,出了作用域析构自动解锁。

LockGuard.hpp代码如下

#pragma once
#include <pthread.h>

// 不定义锁,外部会传递锁
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock)
        : _lock(lock)
    {
    }
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void UnLock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock)
        : _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.UnLock();
    }
private:
    Mutex _mutex;
};

 修改如下

3.多线程版 

多线程版很简单,因为我们的代码本来就是互斥的,因为在生产和消费都进行了上锁。因此直接创建线程就好了。

我们封装一下BlockQueue,多一个线程名字,同时创建两个生产线程与两个消费线程。

 运行可以看到,生产线程一下生产两个,消费线程也一下消费两个,四个一组,很好的配合起来了。

最后附上总代码 

BlockQueue.hpp

#pragma once

#include<iostream>
#include<queue>
#include<pthread.h>
#include "LockGuard.hpp"
using namespace std;

const int defaultcap = 5;

template<class T>
class BlockQueue
{
public:
    BlockQueue(int capacity = defaultcap)
        :_capacity(capacity)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_p_cond,nullptr);
        pthread_cond_init(&_c_cond,nullptr);
    }

    bool IsFull()
    {
        return _q.size()==_capacity;
    }

    bool IsEmpty()
    {
        return _q.size()==0;
    }

    void Push(const T& in)//生产者
    {
        LockGuard lockgaurd(&_mutex);
        // pthread_mutex_lock(&_mutex);
        while(IsFull())
        {
            //阻塞等待
            pthread_cond_wait(&_p_cond,&_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_c_cond);
        // pthread_mutex_unlock(&_mutex);
    }

    void Pop(T* out)    //消费者
    {
        LockGuard lockguard(&_mutex);
        // pthread_mutex_lock(&_mutex);
        while(IsEmpty())
        {
            //阻塞等待
            pthread_cond_wait(&_c_cond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_p_cond);
        // pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }
private:
    queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond;//生产者条件变量
    pthread_cond_t _c_cond;//消费者条件变量
};

string Getname(int i)
{
    string s = "pthread_";
    s+=to_string(i);
    return s;
}


template<class T>
class ThreadData
{
public:
    ThreadData(BlockQueue<T>* bq)
        :_bq(bq)
    {
        _name = Getname(i++);
    }
public:
    BlockQueue<T>* _bq;
    string _name;
    static int i;
};

template<class T>
int ThreadData<T>::i = 1;

LockGuard.hpp

#pragma once
#include <pthread.h>

// 不定义锁,外部会传递锁
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock)
        : _lock(lock)
    {
    }
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void UnLock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {
    }

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock)
        : _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.UnLock();
    }
private:
    Mutex _mutex;
};

Main.cc

#include "BlockQueue.hpp"
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>


void *consumer(void *args)
{
    ThreadData<int> *td = static_cast<ThreadData<int> *>(args);
    while (true)
    {
        int data = 0;
        td->_bq->Pop(&data);
        cout<<"consumer data: "<<data<<"线程名: "<<td->_name<<endl;
    }
    return nullptr;
}

void *productor(void *args)
{
    ThreadData<int> *td = static_cast<ThreadData<int> *>(args);
    while (true)
    {
        int data = rand()%10+1;
        td->_bq->Push(data);
        cout<<"productor data:"<<data<<"线程名: "<<td->_name<<endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    srand(time(nullptr));

    BlockQueue<int> *bq = new BlockQueue<int>();
    
    pthread_t c[2], p[2];

    ThreadData<int> *td1 = new ThreadData<int>(bq);
    pthread_create(&c[0], nullptr, consumer, td1);

    ThreadData<int> *td2 = new ThreadData<int>(bq);
    pthread_create(&p[0], nullptr, productor, td2);

    ThreadData<int> *td3 = new ThreadData<int>(bq);
    pthread_create(&c[1], nullptr, consumer, td3);

    ThreadData<int> *td4 = new ThreadData<int>(bq);
    pthread_create(&p[1], nullptr, productor, td4);


    pthread_join(c[0],nullptr);
    pthread_join(p[0],nullptr);
    pthread_join(c[1],nullptr);
    pthread_join(p[1],nullptr);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1583934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SL4010 低压升压恒压芯片 2.7-24V输入 输出30V/10A 300W功率

SL4010是一款高效能、宽电压范围的低压升压恒压芯片&#xff0c;其卓越的性能和广泛的应用领域使其在市场上备受瞩目。该芯片支持2.7-24V的宽输入电压范围&#xff0c;能够提供稳定的30V/10A输出&#xff0c;最大输出功率高达300W&#xff0c;为各种电子设备提供稳定可靠的电源…

蓝桥杯物联网竞赛_STM32L071KBU6_我的全部省赛及历年模拟赛源码

我写的省赛及历年模拟赛代码 链接&#xff1a;https://pan.baidu.com/s/1A0N_VUl2YfrTX96g3E8TfQ?pwd9k6o 提取码&#xff1a;9k6o

还不会免费将PDF转为Word?赶快试试这3种工具!

PDF文档格式转换是高频且刚需的办公需求&#xff0c;虽然很简单&#xff0c;但其实绝大部分人找不到合适的工具。 将PDF免费转为Word的方法有很多&#xff0c;这里主要介绍三种工具。 第一种使用最常见的Word软件&#xff0c;第二种使用免费转换网站pdf2doc&#xff0c;第三种…

算法打卡day41|动态规划篇09| Leetcode198.打家劫舍、213.打家劫舍II、337.打家劫舍 III

算法题 Leetcode 198.打家劫舍 题目链接:198.打家劫舍 大佬视频讲解&#xff1a;198.打家劫舍视频讲解 个人思路 偷还是偷&#xff0c;这取决于前一个和前两个房是否被偷了&#xff0c;这种存在依赖关系的题目可以用动态规划解决。 解法 动态规划 动规五部曲&#xff1a;…

李廉洋:4.9黄金屡创新高。黄金原油晚间最新分析建议。

但当下不管是战争因素所带来的避险情绪影响还是美国降息与否所带来的经济影响都无疑还是支撑着黄金继续走高&#xff0c;那么接下来&#xff0c;只要市场不出现较大的利空影响&#xff0c;黄金都不会有较大的回调力度&#xff0c;所以我们当下不管是短线还是长线仍旧以继续看多…

【LAMMPS学习】八、基础知识(1.6) LAMMPS 与其他代码耦合

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

外包干了3天,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;19年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

蓝桥杯 每日2题 day4

碎碎念&#xff1a;好难好难&#xff0c;&#xff0c;发呆两小时什么也写不出来&#xff0c;&#xff0c;&#xff0c;周六大寄了 10.阶乘约数 - 蓝桥云课 (lanqiao.cn) 暴力跑了两个小时没出来结果&#xff0c;&#xff0c;去看题解要用数学&#xff1a;约数定理&#xff0c…

成功解决> 错误: 无效的源发行版:17

运行项目的时候出现下面的报错&#xff1a; Execution failed for task ‘:device_info_plus:compileDebugJavaWithJavac’. 错误: 无效的源发行版&#xff1a;17 原因&#xff1a;没有设置好自己项目的JDK版本 解决&#xff1a;1.检查自己项目的JDK版本 将自己的项目改为JDK 1…

【RSGIS数据资源】2010s中国陆地生态系统碳密度数据集

文章目录 摘要引言数据采集和处理方式数据来源数据整理 数据样本描述数据质量控制与评估数据使用方法和建议 摘要 本数据集覆盖了森林、草地、农田、湿地和灌丛等主要生态系统类型&#xff0c;包含了植被地上碳密度、植被地下碳密度和不同深度&#xff08;0–20 cm和0–100 cm…

【LAMMPS学习】八、基础知识(1.7) LAMMPS 与 MDI 库代码耦合

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

计算机网友将饭卡余额改成100多万

你在学校干过最疯狂的事是什么&#xff1f; 一位学计算机的网友说&#xff0c;他改造过的水卡和饭卡都能无限使用&#xff0c;两年后在食堂刷卡&#xff0c;被食堂阿姨发现余额竟然还剩一百多万&#xff0c;虽然没有赔钱&#xff0c;但是被学校教务处处分了&#xff0c;怎么说…

【C++基础】运算符和流程控制语句

C中的运算符和流程控制语句 一、运算符1. C和Java在通用运算符中的不同之处对比2. C中的位运算符2.1 移位运算符2.2 位逻辑运算符 3. 运算时的类型转换总结3.1 隐式类型转换3.2 显式类型转换&#xff08;强制类型转换&#xff09; 4. 注意 二、流程控制语句1. C和Java在通用流程…

微服务架构下的配置管理:Go 语言与 YAML 的完美结合

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等&#xff0c;您的关注将是我的更新动力&#xff01; 在微服务架构盛行的今天&#xff0c;每个服务都是独立部署的实体&#xff0c;它们通常拥有各自的配置需求。应用程序配置文件扮演…

FPN(Feature Pyramid Network)详解

文章涉及个人理解部分&#xff0c;可能有不准确的地方&#xff0c;敬请指正 0. 概述 FPN&#xff0c;全名Feature Pyramid Networks&#xff0c;中文称为特征金字塔网络。它是2017年cvpr上提出的一种网络&#xff0c;主要解决的是目标检测中的多尺度问题。FPN通过简单的网络连…

【Android】【root remount】【3】remount 文件详细信息获取

前言 我们在root & remount 设备后&#xff0c;push相关文件到systm 、vendor、product 等目录进行调试&#xff0c;那么我们push的文件被保存在什么地方呢&#xff1f; 以及我们FWS、app侧如何过去push 的文件信息呢&#xff1f; remount push 文件保存 push 文件保存的…

【汇编语言实战】输入2个整数求最大公约数

C语言描述该程序流程&#xff1a; //辗转相除法 #include <stdio.h> int main() {int a,b;scanf("%d %d",&a,&b);while(a-b!0){if(a>b){aa-b;}else{bb-a;}}printf("%d",a); }汇编语言&#xff1a; include irvine32.inc .data a dword …

rk3588开发板ubuntu系统下安装ftp服务器及windows客户端建立连接

出发点:两台主机之间通过ftp进行文件传输 一.服务端安装 在FileZilla官网上找到服务端版本: 支持x86-64 查询rk3588的芯片架构: uname -m 芯片架构为aarch64,因此该软件不支持。 更换为:vsftpd 操作步骤为: 1)安装:sudo apt install vsftpd 2)启动:sudo syste…

DataX 数据库同步部分源码解析

在工作中遇到异构数据库同步的问题,从Oracle数据库同步数据到Postgres&#xff0c;其中的很多数据库表超过百万&#xff0c;并且包含空间字段。经过筛选&#xff0c;选择了开源的DataXDataX Web作为基础框架。DataX 是阿里云的开源产品&#xff0c;大厂的产品值得信赖&#xff…

Day1 省选衔接题 思路总结

Day1 省选题 思路 取数 可反悔的贪心。我们开一个双向链表记录此时每个数的前/后一个数是什么。一个简单但不一定正确的贪心策略即为&#xff1a;每次都取走当前值最大的且可取的数&#xff0c;并更新列表。考虑如何使这个贪心思路正确。 设 p r e x pre_x prex​ 表示 x x …