C++多线程编程——线程同步(保姆级-1.4W字)

news2024/11/19 0:20:02

目录

C++线程同步

引入

互斥锁

std::mutex

std::lock_guard类模板 

unique_lock

成员方法

应用举例

std::lock()函数模板

std::call_once(flag、function)

懒汉式单例模式实例

unique_lock互斥锁方式

​编辑 

call_once方式

条件变量

std::condition

条件变量使用实例

原子操作

读写atomic对象的值

原子操作使用实例

内存模型:强顺序与弱顺序


C++线程同步

引入

线程同步是一种编程技术,它用于在多线程环境中确保多个线程能够正确、安全地共享和访问共享资源。线程同步的主要目的是防止数据竞争和不一致性,以及避免多个线程同时对同一数据进行修改或访问导致的问题。

以下是一些需要实现线程同步的情况:

  1. 数据竞争:当多个线程同时访问和修改共享资源时,可能导致数据不一致或不可预测的结果。例如,如果两个线程都尝试修改同一个变量的值,其中一个线程的修改可能会被另一个线程覆盖,导致数据错误。
  2. 死锁:当多个线程相互等待对方释放资源时,会导致程序陷入死锁状态,无法继续执行。例如,线程A等待线程B释放资源,而线程B正在等待线程A释放资源,导致两个线程都无法继续执行。
  3. 资源竞争:当多个线程同时访问共享资源时,可能会导致资源争用,影响程序的性能和响应时间。例如,多个线程同时访问同一个文件或数据库连接,可能会导致读写冲突和性能下降。

因此,实现线程同步的目的是确保多个线程能够正确地访问和修改共享资源,避免数据竞争、死锁和资源竞争等问题。常用的线程同步技术包括互斥锁、信号量、条件变量、读写锁等。

C++11对于线程同步提供了四种机制,分别是

  • 互斥锁
  • std::call_once(flag、function)
  • 条件变量
  • 原子操作

以两个线程对文件进行写入操作为例,如果不加以同步操作,由于线程时间片调度机制,会产生线程竞态,从而会导致写入的数据顺序发生混乱

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=50;
        while(num--)
        {
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
        }

    }

    void myWriter2(int fd)
    {
        int num=50;
        while(num--)
        {
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
        }
    }
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

互斥锁

std::mutex

不同于C语言的互斥锁,C++的互斥锁定义完不需要初始化和销毁,直接使用即可

如下:对之前的多线程文件写入程序进行上锁,解决资源竞态的问题

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=50;
        while(num--)
        {
            my_mutex.lock();
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
            my_mutex.unlock();
        }

    }

    void myWriter2(int fd)
    {
        int num=50;
        while(num--)
        {
            my_mutex.lock();
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
            my_mutex.unlock();
        }
    }
    std::mutex my_mutex;
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

std::lock_guard类模板 

  • std::mutex有时候lock()完,忘记unlock()会导致死锁的问题

如下图中,使用break跳出循环会跳过解锁,从而导致死锁问题

  • std::lock_guard不再使用lock、unlock,可以用于解决死锁问题

即使用lock_guard创建一个类模板对象,它会通过构造和析构的方式帮我们自动进行上锁解锁

lock_guard会在定义的时候上锁,出了它的当前作用域就会自动解锁(析构)

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=50;
        while(num--)
        {
            lock_guard<mutex>my_lock(my_mutex);
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
        }

    }

    void myWriter2(int fd)
    {
        int num=50;
        while(num--)
        {
            lock_guard<mutex>my_lock(my_mutex);
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
        }
    }
    std::mutex my_mutex;
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

unique_lock

unique_lock是一个类模板,使用等同于std::lock_guard

成员方法

构造函数unique_lock的第二个参数

  • std::adopt_lock表示这个互斥量已经被lock了(你必须要把互斥量提前lock了,否则会报异常)
  • std::try_to_lock:尝试用mutex的lock()方法去锁定这个mutex,但如果没有锁定成功,也会立即返回

lock(),加锁:unique_lock创建的对象锁,可以在作用域内任意地方上锁,一般用于unlock()解锁后,再次上锁

unlock(),解锁:unique_lock创建的对象锁,可以在作用域内任意地方解锁,而不通过离开作用域析构解锁

try_lock(),尝试给互斥量加锁,如果拿不到锁,返回false,如果拿到了锁,返回true,这个函数是不阻塞的

总结:lock_guard不够灵活,它只能保证在析构的时候执行解锁操作;而unique_lock内部需要维护锁的状态,所以效率要比lock_guard低一点,在lock_guard能解决问题的时候,就用lock_guard

应用举例

实例1:多线程文件写入

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=50;
        while(num--)
        {
            unique_lock<mutex>my_lock(my_mutex);
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
            my_mutex.unlock();
        }

    }

    void myWriter2(int fd)
    {
        int num=50;
        while(num--)
        {
            unique_lock<mutex>my_lock(my_mutex);
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
        }
    }
    std::mutex my_mutex;
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

实例2:使用adopt_lock时,unique_lock定义时将不会自动上锁,需要我们用lock()方法指定地方进行上锁,否则无法上锁导致数据混乱

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=100;
        while(num--)
        {
            //my_mutex.lock();
            unique_lock<mutex>my_lock(my_mutex,std::adopt_lock);
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
            my_mutex.unlock();
        }

    }

    void myWriter2(int fd)
    {
        int num=100;
        while(num--)
        {
            unique_lock<mutex>my_lock(my_mutex);
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
        }
    }
    std::mutex my_mutex;
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

std::lock()函数模板

功能:一次锁住两个或者两个以上的互斥量(至少两个,多了不限),解决锁的顺序问题导致死锁的风险

解锁

  • 方法1:unlock
  • 方法2:std::lock_guard()的std::adopt_lock参数

举例:我们在创建多个锁的时候,上锁和解锁的顺序一定要相同,否则会导致死锁的问题

比如在下面程序中,线程1在解锁完锁2的时候,线程2会上锁2,这样就会导致线程1中无法对锁1解锁,从而在线程2中对锁1也无法上锁,这样就导致死锁,两个线程同时发生阻塞

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=100;
        while(num--)
        {   
            my_mutex1.lock();
            my_mutex2.lock();
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
            my_mutex2.unlock();
            my_mutex1.unlock();
        }

    }

    void myWriter2(int fd)
    {
        int num=100;
        while(num--)
        {
            my_mutex2.lock();
            my_mutex1.lock();
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
            my_mutex1.unlock();
            my_mutex2.unlock();
        }
    }
    std::mutex my_mutex1,my_mutex2;
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

但我们使用类模板提供的lock()方法,就不用关心上锁和解锁顺序问题,解锁先后与上锁无关

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num=100;
        while(num--)
        {   
            lock(my_mutex1,my_mutex2);
            write(fd,"hello",5);
            write(fd,"world",5);
            write(fd,"\n",1);
            my_mutex2.unlock();
            my_mutex1.unlock();
        }

    }

    void myWriter2(int fd)
    {
        int num=100;
        while(num--)
        {
            lock(my_mutex1,my_mutex2);
            write(fd,"nan",3);
            write(fd,"jing",4);
            write(fd,"\n",1);
            my_mutex1.unlock();
            my_mutex2.unlock();
        }
    }
    std::mutex my_mutex1,my_mutex2;
};

int main(int argc, char const *argv[])
{
    int fd = open("file",O_CREAT | O_WRONLY,0655);

    if(fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1,&f,fd);
    thread t2(&FileWriter::myWriter2,&f,fd);

    t1.join();
    t2.join();
    return 0;
}

std::lock_guard的std::adopt_lock参数确保在构造时不会再次锁定互斥锁,而是假设这些互斥锁已经被锁定,并在析构时自动解锁

#include <iostream>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
using namespace std;

class FileWriter
{
public:
    void myWriter1(int fd)
    {
        int num = 100;
        while (num--)
        {
            std::lock(my_mutex1, my_mutex2);
            std::lock_guard<std::mutex> lock1(my_mutex1, std::adopt_lock);
            std::lock_guard<std::mutex> lock2(my_mutex2, std::adopt_lock);

            write(fd, "hello", 5);
            write(fd, "world", 5);
            write(fd, "\n", 1);
        }
    }

    void myWriter2(int fd)
    {
        int num = 100;
        while (num--)
        {
            std::lock(my_mutex1, my_mutex2);
            std::lock_guard<std::mutex> lock1(my_mutex1, std::adopt_lock);
            std::lock_guard<std::mutex> lock2(my_mutex2, std::adopt_lock);

            write(fd, "nan", 3);
            write(fd, "jing", 4);
            write(fd, "\n", 1);
        }
    }
    std::mutex my_mutex1, my_mutex2;
};

int main(int argc, char const *argv[])
{
    int fd = open("file", O_CREAT | O_WRONLY, 0655);

    if (fd == -1)
    {
        perror("open file error!");
        exit(-1);
    }

    FileWriter f;
    thread t1(&FileWriter::myWriter1, &f, fd);
    thread t2(&FileWriter::myWriter2, &f, fd);

    t1.join();
    t2.join();
    return 0;
}

std::call_once(flag、function)

互斥锁的最大问题是频繁的上锁解锁造成的开销比较大

std::call_once()功能是能够保证函数function只被调用一次,具备互斥量这种能力,而且效率上比互斥量消耗的资源更小

懒汉式单例模式实例

unique_lock互斥锁方式

#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <list>
#include <mutex>

using namespace std;

std::mutex resource_mutex;

class MyCAS
{
private:
    MyCAS(){}//私有化了的构造函数
private:
    static MyCAS *m_instance;//静态成员变量
public:
    static MyCAS *GetInstance()
    {
        //提高效率
        //a)如果if(m_instance != NULL)条件成立,则表示肯定m_instance已经被new过了
        //b)如果if(m_instance == NULL),不代表m_instance一定没被new过

        if(m_instance == NULL)//双重锁定(双重检查)
        {
            std::unique_lock<std::mutex>mymutex(resource_mutex);
            if(m_instance == NULL)
            {
                m_instance == new MyCAS();
                static CGarhuishou cl;
            }
        }
        return m_instance;
    }

    class CGarhuishou//类中套类,用来释放对象
    {
    public:
        ~CGarhuishou()//类的析构函数中
        {
            if(MyCAS::m_instance)
            {
                delete MyCAS::m_instance;
                MyCAS::m_instance = NULL;
            }
        }

    };
    void func()
    {
        cout << "测试"<<endl;
    }
};

//类静态变量初始化
MyCAS *MyCAS::m_instance = NULL;

//线程入口函数
void mythread()
{
    cout << "我的线程开始执行了" <<endl;
    MyCAS *p_a = MyCAS::GetInstance(); //这里可能就有问题了
    cout << "我的线程开始执行了" <<endl;
}

int main()
{
    std::thread myobj1(mythread);
    std::thread myobj2(mythread);

    myobj1.join();
    myobj2.join();
    
    return 0;
}

call_once方式

#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <list>
#include <mutex>

using namespace std;

std::mutex resource_mutex;
std::once_flag g_flag; //这是一个系统定义的标记

class MyCAS
{
    static void CreateInstance()//只被调用一次
    {
        std::chrono::microseconds dura(20000);
        std::this_thread::sleep_for(dura);
        m_instance == new MyCAS();
        static CGarhuishou cl;
    }

private:
    MyCAS(){}//私有化了的构造函数
private:
    static MyCAS *m_instance;//静态成员变量
public:
    static MyCAS *GetInstance()
    {
        //两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕CrateInstance(),这里可以把g_flag看做一把锁
        std::call_once(g_flag,CreateInstance);
        cout << "call_once()执行完毕"<<endl;
        return m_instance;
    }

    class CGarhuishou//类中套类,用来释放对象
    {
    public:
        ~CGarhuishou()//类的析构函数中
        {
            if(MyCAS::m_instance)
            {
                delete MyCAS::m_instance;
                MyCAS::m_instance = NULL;
            }
        }

    };
    void func()
    {
        cout << "测试"<<endl;
    }
};

//类静态变量初始化
MyCAS *MyCAS::m_instance = NULL;

//线程入口函数
void mythread()
{
    cout << "我的线程开始执行了" <<endl;
    MyCAS *p_a = MyCAS::GetInstance();
    cout << "我的线程开始执行了" <<endl;
}

int main()
{
    std::thread myobj1(mythread);
    std::thread myobj2(mythread);

    myobj1.join();
    myobj2.join();
    
    return 0;
}

条件变量

std::condition

前提:需要有互斥锁的支持

等待条件:wait(mutex,lambda)

  • 如果第二个参数lambda表达式返回值是true,那么wait()直接返回;
  • 如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并且阻塞到本条件变量成立

唤醒:notify_once()只能通知一个线程

唤醒:notify_all()通知所有线程

条件变量使用实例

打印ABC

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <unistd.h>

using namespace std;

class Print
{
public:
    void printA()
    {
        while(1)
        {
            //lock_guard<mutex>my_lock(my_mutex);
            unique_lock<mutex>my_lock(my_mutex);
            my_cond.wait(my_lock,[this](){
                if(count==0)
                {  
                    return true;
                }
                return false;
            });
            cout << "A" <<endl;
            count =1 ;
            my_cond.notify_all();
            sleep(1);
        }
    }
    void printB()
    {
        while(1)
        {
            //lock_guard<mutex>my_lock(my_mutex);
            unique_lock<mutex>my_lock(my_mutex);
            my_cond.wait(my_lock,[this](){
                if(count==1)
                {  
                    return true;
                }
                return false;
            });
            cout << "B" <<endl;
            count =2 ;
            my_cond.notify_all();
            sleep(1);
        }
    }
    void printC()
    {
        while(1)
        {
            //lock_guard<mutex>my_lock(my_mutex);
            unique_lock<mutex>my_lock(my_mutex);
            my_cond.wait(my_lock,[this](){
                if(count==2)
                {  
                    return true;
                }
                return false;
            });
            cout << "C" <<endl;
            count =0 ;
            my_cond.notify_all();
            sleep(1);
        }
    }
    int count;

    mutex my_mutex;
    condition_variable my_cond;
};


int main()
{
    Print p;

    p.count =0;
    thread t1(&Print::printA,&p);
    thread t2(&Print::printB,&p);
    thread t3(&Print::printC,&p);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

原子操作

原子操作:是在多线程中不会被打断的程序片段(汇编指令);原子操作比互斥量更胜一筹;

作用:原子操作一般用来保护单一变量,不保护代码段

std::atomic

读写atomic对象的值

  • load():以原子方式读atomic对象的值
  • store():以原子方式写入内容

我们也可以直接读写原子变量的值,但是使用提供的成员方法更安全

atomic<int>atm;
atm=0;
cout<<atm<<endl;//读取atm是原子操作,但是整个这一行代码不是原子操作

auto atm2(atm.load());//以原子方式读取atomic对象的值
atm.store(12);//以原子方式写入内容

原子操作使用实例

按理说两个线程都加一百万次,结果应该是二百万,但实际的结果却不是。因为++实际上是两个操作,先加1,然后给变量赋值,因此并不是原子操作,所以会导致两个线程竞态,导致结果变小。

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <future>
#include <atomic>
#include <list>
#include <string>

using namespace std;

//我们封装了一个类型为int的对象,像操作一个int类型变量一样来操作这个g_mycont
//std::atomic<int>g_mycont(0);

int g_mycont = 0;

void mythread()
{
    for(int i =0 ;i<1000000;i++)
    {
        g_mycont++;//对应的操作是个原子操作(不会被打断)
                   //g_mycont += 1;
                   //g_mycont = g_mycont + 1; //结果不对,不是原子操作
    }
}

int main()
{
    thread myobj1(mythread);
    thread myobj2(mythread);

    myobj1.join();
    myobj2.join();

    cout << "两个线程执行完毕,最终的g_mycont的结果是:"<<g_mycont<<endl;
    return 0;
}

通过加锁可以解决问题

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <future>
#include <atomic>
#include <list>
#include <string>

using namespace std;

//我们封装了一个类型为int的对象,像操作一个int类型变量一样来操作这个g_mycont
//std::atomic<int>g_mycont(0);

int g_mycont = 0;
mutex my_mutex;
void mythread()
{
    for(int i =0 ;i<1000000;i++)
    {
        my_mutex.lock();
        g_mycont++;//对应的操作是个原子操作(不会被打断)
                   //g_mycont += 1;
                   //g_mycont = g_mycont + 1; //结果不对,不是原子操作
        my_mutex.unlock();
    }
}

int main()
{
    thread myobj1(mythread);
    thread myobj2(mythread);

    myobj1.join();
    myobj2.join();

    cout << "两个线程执行完毕,最终的g_mycont的结果是:"<<g_mycont<<endl;
    return 0;
}

C++11支持将变量申明为原子值

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <future>
#include <atomic>
#include <list>
#include <string>

using namespace std;

//我们封装了一个类型为int的对象,像操作一个int类型变量一样来操作这个g_mycont
std::atomic<int>g_mycont(0);

void mythread()
{
    for(int i =0 ;i<1000000;i++)
    {
        g_mycont++;//对应的操作是个原子操作(不会被打断)
                   //g_mycont += 1;
                   //g_mycont = g_mycont + 1; //结果不对,不是原子操作
    }
}

int main()
{
    thread myobj1(mythread);
    thread myobj2(mythread);

    myobj1.join();
    myobj2.join();

    cout << "两个线程执行完毕,最终的g_mycont的结果是:"<<g_mycont<<endl;
    return 0;
}

注:atomic原子操作仅对于++、--、&=、|=是支持的 

内存模型:强顺序与弱顺序

强顺序保证了程序的正确性,但效率会很低,一般编译器优化的时候都是按照弱顺序来执行

  • 强顺序模型(又叫TSO,Total Strong Order),是一种靠向程序顺序的顺序模型
  • 弱内存模型(简称WMO,Weak Memory Ordering),是把是否要求强制顺序这个要求直接交给程序员的方法
  • C++11的std::memory_order有6个枚举值,其中有四个常用
    • 顺序一致性顺序std::memory_order_seq_cst
    • 松散顺序std::memory_order_relaxed
    • 释放顺序std:memory_order_acquire&std::memory_order_release
    • std::memory_order_consume本线程所有后续有关本操作的必须在本操作完成后执行

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

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

相关文章

017-文件

017-文件 一、C文件的有关概念 1.什么是文件 文件是指存储在外存储器上的数据的集合。 操作系统是以文件为单位对数据进行管理的。输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流(stream),即输入输出流。C语言把文件看作是一个…

低功耗串口wifi模块在智能工厂解决方案中的数据透传应用

智能工厂解决方案必然是依赖于产品自身去完成信息采集&#xff0c;数据上传。目前基于串口WiFi模块的智慧工厂解决方案应用极为广泛。 工业物联网与串口WiFi模块 工业物联网是在制造和工业环境中&#xff0c;将连网仪器、传感器和其他设备与机械和流程连接起来的实现。创新的…

K8S cluster with multi-masters on Azure VM

拓扑参考&#xff1a; 在 Azure VM 实例上部署 KubeSphere 基础模板 需要修改 IP 地址和 VM Image的可以在模板中修改。 {"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#","contentVersion": &q…

RecyclerView面试问答

RecycleView 和 ListView对比: 使用方法上 ListView:继承重写 BaseAdapter,自定义 ViewHolder 与 converView优化。 RecyclerView: 继承重写 RecyclerView.Adapter 与 RecyclerView.ViewHolder。设置 LayoutManager 来展示不同的布局样式 ViewHolder的编写规范化,ListVie…

【数仓建设系列之三】数仓建模方式及如何评估数仓完善性

【数仓建设系列之三】数仓建模方式及如何评估数仓完善性 上篇文章我们对数仓的分层架构及核心概念做了简单介绍&#xff0c;同时也指明DW层是数仓建模的核心层。本篇文章&#xff0c;将详细从常见的维度模型建设手段及如何评估数仓建设的完善性展开讨论。 一、数仓维度建模 ​…

Azure - AzCopy学习

使用 AzCopy 将本地数据迁移到云存储空间 azcopy login 创建存储账号 ./azcopy login --tenant-id 40242385-c249-4746-95dc-4a0b64d49dc5这里的—tenant-id 在下面的地方查看&#xff1a;目录 ID&#xff1b;需要拥有Storage Blob Data Owner 的权限账号下可能会有很多目录&am…

SMC_TRAFOF_5Axes (FB)_标准龙门5轴

正计算&#xff1a; 电机位置》空间位姿 的正计算。 用于图形5轴控件的展示。 刀具长度 [u] 【脉冲当量】 电机转1圈是5毫米 电机转1圈要50000脉冲 如果刀具长5毫米&#xff0c;那么刀具长度u50000脉冲当量 输入输出&#xff1a; X轴 Y轴 Z轴 A轴【绕z轴旋转】东南西北方…

SFTP和SCP:哪种才是企业文件传输的可靠选择

文件传输在企业发展和业务拓展中越来越重要。为了保证数据的安全和传输的效率&#xff0c;企业需要选择合适、安全且高效的文件传输方式。在这种情况下&#xff0c;SFTP和SCP成为了企业文件传输的热门选择。 本文将详细介绍SFTP和SCP的特点&#xff0c;以及它们如何成为企业文…

ubuntu学习(四)----文件写入操作编程

1、write函数的详解 ssize_t write(int fd,const void*buf,size_t count); 参数说明&#xff1a; fd:是文件描述符&#xff08;write所对应的是写&#xff0c;即就是1&#xff09; buf:通常是一个字符串&#xff0c;需要写入的字符串 count&#xff1a;是每次写入的字节数…

【学习FreeRTOS】第19章——FreeRTOS低功耗模式Tickless

1.低功耗模式简介 很多应用场合对于功耗的要求很严格&#xff0c;比如可穿戴低功耗产品、物联网低功耗产品等一般MCU都有相应的低功耗模式&#xff0c;裸机开发时可以使用MCU的低功耗模式。FreeRTOS也提供了一个叫Tickless的低功耗模式&#xff0c;方便带FreeRTOS操作系统的应…

每日汇评:由于鲍威尔鹰派的讲话,黄金可能重新回到 1900 美元区域

1、金价暂停了四天的上涨趋势&#xff0c;但有望创下六周以来最好的一周&#xff1b; 2、在鲍威尔在杰克逊霍尔研讨会上发表讲话之前&#xff0c;美元恢复平静&#xff1b; 3、在 RSI 看跌的情况下&#xff0c;金价与 21 日移动平均线阻力位 1919 美元作斗争&#xff1b; 金…

免费PPT素材网站,我推荐这6个

找PPT素材、模板&#xff0c;就上这6个网站&#xff0c;免费下载&#xff0c;建议收藏~ 菜鸟图库 https://www.sucai999.com/search/ppt/0_0_0_1.html?vNTYwNDUx 菜鸟图库网有非常丰富的免费素材&#xff0c;像设计类、办公类、自媒体类等素材都很丰富。PPT模板种类很多&…

适配小程序隐私保护指引设置

由于小程序发布了一个公告&#xff0c;那么接下来就是怎么改简单的问题了。毕竟不太想大的改动历史上的代码。尽量简单的适配隐私策略就可以了。 整体思路也是参考现在App普遍的启动就让用户同意隐私策略&#xff0c;不同意不让用&#xff0c;同意了之后才能够继续使用。 公告…

英伟达挖走小鹏汽车高管吴新宙!何小鹏亲自送行 | 百能云芯

8月25日消息&#xff0c;英伟达公司挖来小鹏汽车自动驾驶副总裁吴新宙&#xff0c;任命他为自动驾驶产品主管&#xff0c;该职位将于8月25日正式生效。 据悉&#xff0c;小鹏汽车董事长何小鹏在个人微博上晒出了一张与吴新宙以及英伟达CEO黄仁勋的合影&#xff0c;并透露即将展…

访学、博后参考|加拿大十大名校简介

上期我们介绍了加拿大各省所在大学的分布情况&#xff0c;本期知识人网小编整理出加拿大十大名校简介&#xff0c;供申请者参考。 1、多伦多大学&#xff08;University of Toronto&#xff09;&#xff08;University of Toronto&#xff09;&#xff08;University of Toront…

最新绕过目标域名CDN进行信息收集技术

绕过目标域名CDN进行信息收集 1&#xff0e;CDN简介及工作流程 CDN&#xff08;Content Delivery Network&#xff0c;内容分发网络&#xff09;的目的是通过在现有的网络架构中增加一层新的Cache&#xff08;缓存&#xff09;层&#xff0c;将网站的内容发布到最接近用户的网…

一次harbor升级导致镜像项目访问无权限问题

一、问题背景 将环境中现运行的harbor版本升级到2.6.2版本&#xff0c;相关同事升级完&#xff0c;发现有部分镜像项目点进去报无权限问题&#xff0c;镜像项目无法使用&#xff0c;但是也有部分项目是可以正常提供使用的。 二、问题处理过程 1、根据报错反馈没权限&#xff…

Thinkphp内核微信拼团购物商城小程序源码

Thinkphp内核开发的微信拼团购物商城小程序源码&#xff0c;支持微信支付&#xff0c;站长亲测完美。 下载地址&#xff1a;https://bbs.csdn.net/topics/616764816

电脑文件删除了可以找回吗?分享一种简单恢复删除电脑文件办法!

电脑文件删除了可以找回吗&#xff1f;可以。在原理上讲电脑删除的文件是有希望恢复的&#xff0c;因为操作系统在删除文件的时候并会不会立刻将文件彻底删除。当文件被删除的时候&#xff0c;其文件记录被删除&#xff0c;并且被文件占用的磁盘空间被标记为空闲。 这样对于用户…

【HCIP】16.MPLS LDP

LDP是MPLS的一种控制协议&#xff0c;相当于传统网络中的信令协议&#xff0c;负责FEC的分类、标签的分配以及LSP的建立和维护等操作。LDP规定了标签分发过程中的各种消息以及相关处理过程。 LDP的工作过程主要分为两部分&#xff1a; LSR之间建立LDP会话。LSR之间基于LDP会话…