Windows环境下的多线程编程(上)C++

2023-05-16

1.为什么要用多线程

任务分解:

耗时的操作,任务分解,实时响应

数据分解:

充分利用多核CPU处理数据

数据流分解:

读写分流,解耦合设计

2.并发,进程,线程的基本概念

2.1 并发:两个或者两个以上的独立任务同时进行:一个程序同时执行多个独立的任务;

对于单核计算机来说,某一时刻只能执行一个任务:由操作系统调度,每秒钟进行多次的所谓的“任务切换”,对每个任务设置一个执行时间,到了时间之后切换到另一个任务,就这样来回切换,来实现并发,这种叫并发假象,也就是所谓的“上下文切换”。这种切换时间花销很大。

对于多核计算机来说,可真正实行并发任务,但一般情况下,多核处理器的也需要进行来回切换,因为任务一般可达到好几千个,而处理器一般就是2核,4核,6核,8核等。

2.2 进程:运行起来的可执行程序。(在任务管理器->用户中可以看到)

2.3 线程:用来执行代码的

  • 每个进程中只有一个主线程
  • 当执行一个可执行文件时,这个主线程就自动启动了,相当于是主线程来执行该代码,主线程与进程有你有我,无我无你。

除了主线程之外,还可以自己写代码来创建其他线程,及多线程(并发)每个线程都需要一个独立的堆栈空间(1M), 不宜太多。不然来回切换会耗费程序运行的时间。

3.并发的实现:

  1. 多进程实现
  2. 多线程实现(自己写代码实现)

 多进程并发:

word启动后就是进程,浏览器启动后就是进程。账号服务器启动就是一个进程,游戏逻辑服务器启动后又是一个进程,就叫多进程。进程与进程之间的通讯:在一台电脑上通讯手段有:管道,文件,消息队列,共享内存等;不在同一台电脑上的通信手段:socket通信技术。

多线程并发:

在单个进程中创建多个线程,线程可以看作是轻量级的进程,一个进程中的所有线程共享内存,使用多线程开销远远小于多进程;但共享内存一般会带来数据一致性问题,比如两个线程同时传递数据时会引起混乱,所以在写线程时要注意先后。传统情况下:主线程结束,子线程必须结束掉,不然就是不合格的程序,一般都应该是主线程等子线程,再自己执行。但在C++11新标准下不用,如detach()的使用。

4.创建线程 

 C++11新标准线程库

创建线程函数:windows下:CreateThread(),_beginthted(),_beginthredexe()

POSIX thread(pthread)库:配置后可跨平台使用;

C++11新标准支持跨平台使用。 

一般情况下也可用thread创建线程,但需要引入thread库

主线程从main()函数开始,那么自己写的线程应该也从一个函数开始运行(初始函数)

新标准下detach()函数:

主线程不再与子线程汇合,主线程可以先结束,不用等待子线程,但不建议经常使用。因为当主线程运行完后,子线程很有可能执行不完。

windows运行thread时,一定要配置好mingw.

官网地址:MinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.nethttps://sourceforge.net/projects/mingw-w64/files/①点击File

 ②下载离线包,因为在线安装的话很慢很慢

 x86_64是64位的,想要运行多线程就必须选择posix,所以我选择的是第二个,直接下载解压即可

③配置环境变量

此电脑---属性---高级系统设置---环境变量---设置系统变量---双击Path将mingw64下的bin路径粘贴复制到Path处,点击确定即就配置好了。

范例演示:

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
    cout << "我的线程开始运行" << endl;
    //-------------
    //-------------
    cout << "我的线程运行完毕" << endl;
}

int main()
{
    //(1)创建了线程,线程执行起点(myPrint)是myPrint;(2)执行线程
    thread myThread(myPrint);//thread标准库中的一个类,用来创建线程

    myThread.join();//阻塞主线程,让主线程等待子线程完成执行,然后子线程与主线程汇合,主线程继续
    
    if (myThread.joinable())
    {
        cout << "可以调用可以调用join()或者detach()" << endl;
    }
    else
    {
        cout << "不能调用可以调用join()或者detach()" << endl;
    }

    cout << "Hello World!" << endl;

    system("pause");
    return 0;
}

5.其他创建线程手法:

类对象范例演示:

#include <iostream>
#include <thread>
using namespace std;

class Ta
{
public:
    void operator()()
    {
        cout << "我的线程开始运行" << endl;
        //-------------
        //-------------
        cout << "我的线程运行完毕" << endl;
    }
};

int main()
{
    // main函数里的:
    Ta ta;
    thread myThread(ta);
    myThread.join();

    cout << "Hello World!" << endl;

    system("pause");
    return 0;
}

 用lambda表达式创建线程:

范例演示:

#include <iostream>
#include <thread>

using namespace std;

int main()
{
    auto lambdathread = []
    {
        cout << "线程开始" << endl;
        //...
        cout << "线程结束" << endl;
    };
    thread myThread(lambdathread);
    myThread.join();

    cout << "Hello World!" << endl;

    system("pause");
    return 0;
}

6.传递临时对象作线程参数

6.1 陷阱1范例展示:

#include <iostream>
#include <thread>
using namespace std;

void myPrint(const int &i, char* pmybuf)
{
	//如果线程从主线程detach了
	//i不是mvar真正的引用,实际上值传递,即使主线程运行完毕了,子线程用i仍然是安全的,但仍不推荐传递引用
	//推荐改为const int i
	cout << i << endl;
	//pmybuf还是指向原来的字符串,所以这么写是不安全的
	cout << pmybuf << endl;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test";
	thread myThread(myPrint, mvar, mybuf);//第一个参数是函数名,后两个参数是函数的参数
	//myThread.join();
	myThread.detach();
	
	cout << "Hello World!" << endl;
}
//引用和指针慎用

6.2 陷阱2范例展示:

#include <iostream>
#include <thread>
#include <string>
using namespace std;

void myPrint(const int i, const string& pmybuf)
{
	cout << i << endl;
	cout << pmybuf << endl;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test";
	//如果detach了,这样仍然是不安全的
	//因为存在主线程运行完了,mybuf被回收了,系统采用mybuf隐式类型转换成string
	//推荐先创建一个临时对象thread myThread(myPrint, mvar, string(mybuf));就绝对安全了。。。。
	thread myThread(myPrint, mvar, mybuf);
	//myThread.join();
	myThread.detach();

	cout << "Hello World!" << endl;
}

6.3 正确演示:

#include <iostream>
#include <thread>
using namespace std;

void myPrint(const int i, const string pmybuf)
{
    cout << i << endl;
    cout << pmybuf << endl;
}

int main()
{
    int mvar = 1; //用来测试对比
    int &mvary = mvar;
    char mybuf[] = "Bid farewell to the epidemic!";
    thread myThread(myPrint, mvar, string(mybuf)); //string(mybuf)临时对象,为避免隐式类型转换
    //myThread.join();
    myThread.detach();

    system("pause");
    return 0;
}
//类对象同理,创建临时对象就可以解决这种问题,须注意的地方是要传递引用

结论:

建议不使用detach(),只使用join(),这样就不存在局部变量失效导致线程对内存的非法引用问题。

 7. 临时对象作为线程参数深入:

线程ID:id是个数字,每个线程对应一个数字,每个线程对应的线程数字不同,可用c++标准库的函数来获取,this_thread::get_id()

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
    int m_i;
    A(int i) : m_i(i) { cout << "构造函数 " << std::this_thread::get_id() << endl; }
    A(const A &i) : m_i(i.m_i) { cout << "复制构造函数 " << std::this_thread::get_id() << endl; }
    ~A(int i) : m_i(i) { cout << "析构函数 " << std::this_thread::get_id() << endl; }
};

void myPrint(const A &pmybuf)
{
    pmybuf.m_i = 199;
    cout << "子线程myPrint的参数地址是" << &pmybuf << "thread = " << std::this_thread::get_id() << endl;
}

int main()
{
    int mm=10;
    // myPrint(const A& pmybuf)中引用不能去掉,如果去掉会多创建一个对象
    // const也不能去掉,去掉会出错
    //即使是传递的const引用,但在子线程中还是会调用拷贝构造函数构造一个新的对象,
    //所以在子线程中修改m_i的值不会影响到主线程
    //如果希望子线程中修改m_i的值影响到主线程,可以用thread myThread(myPrint, std::ref(myObj));
    //这样const就是真的引用了,myPrint定义中的const就可以去掉了,类A定义中的mutable也可以去掉了
    thread myThread(myPrint, A(mm));
    myThread.join();
    // myThread.detach();

    cout << "Hello World!" << endl;

    system("pause");
    return 0;
}
#include <iostream>
#include <thread>
#include <memory>
using namespace std;

//创建一个智能指针
void myPrint(unique_ptr<int> pp)
{
	cout << "thread = " << std::this_thread::get_id() << endl;
}

int main()
{
	unique_ptr<int> up(new int(10));
	//独占式指针只能通过std::move()才可以传递给另一个指针
	//传递后up就指向空,新的pp指向原来的内存
	//所以这时就不能用detach了,因为如果主线程先执行完,pp指向的对象就被释放了
	thread myThread(myPrint, std::move(up));
	myThread.join();
	//myThread.detach();

	return 0;
}

8. 多线程数据共享:

涉及互斥量,锁等概念

8.1 互斥量(mutex)的基本概念

  • 互斥量就是个类对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁,只有一个线程能锁定成功,如果没有锁成功,那么流程将卡在lock()这里不断尝试去锁定。
  • 互斥量使用要小心,保护数据不多也不少,少了达不到效果,多了影响效率。

8.2 互斥量的用法

  • 包含#include <mutex>头文件
  • 1. 成员函数:lock(),unlock()
  • 步骤:1.lock(),2.操作共享数据,3.unlock()。
  • lock()和unlock()要成对使用
  • 2. lock_guard类模板
  • lock_guard<mutex> sbguard(myMutex);可直接取代lock()和unlock()
  • lock_guard构造函数执行了mutex::lock();在作用域结束时,调用析构函数,执行mutex::unlock(),但不如lock()和unlock()灵活

8.3 死锁

A一直在北京死等B,B一直在深圳死等A,那么A,B永远见不了面,就叫死锁。
1 死锁演示
死锁至少有两个互斥量mutex1,mutex2。

  • a.线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
  • b.上下文切换后线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.
  • c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。

2 死锁的一般解决方案:
只要保证多个互斥量上锁的顺序一样就不会造成死锁。

3 std::lock()函数模板

std::lock(mutex1,mutex2……); 同时锁定多个互斥量(这种情况很少见),用于处理多个互斥量。如果互斥量中一个没锁住,它就卡着了,等所有互斥量都锁住,才能继续执行。如果有一个没锁住,就会把已经锁住的释放掉(要么互斥量都锁住,要么都没锁住,防止死锁),别忘了unlock()。


4 std::lock_guard的std::adopt_lock参数

std::lock_guardstd::mutex my_guard(my_mutex,std::adopt_lock);
加入adopt_lock后,在调用lock_guard的构造函数时,不再进行lock();
adopt_guard为结构体对象,起一个标记作用,表示这个互斥量已经lock(),不需要再lock()。

#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;

class A
{
public:
    void inMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            cout << "11111111111111111111111111111" << i << endl;
            {
                // lock_guard<mutex> sbguard(myMutex1, adopt_lock);
                lock(myMutex1, myMutex2);
                // myMutex2.lock();
                // myMutex1.lock();
                msgRecvQueue.push_back(i);
                myMutex1.unlock();
                myMutex2.unlock();
            }
        }
    }
    bool outMsgLULProc()
    {
        myMutex1.lock();
        myMutex2.lock();
        if (!msgRecvQueue.empty())
        {
            cout << "000000000000000000000000000000" << msgRecvQueue.front() << endl;
            msgRecvQueue.pop_front();
            myMutex2.unlock();
            myMutex1.unlock();
            return true;
        }
        myMutex2.unlock();
        myMutex1.unlock();
        return false;
    }

    void outMsgRecvQueue()
    {
        for (int i = 0; i < 100000; ++i)
        {
            if (outMsgLULProc())
            {
            }
            else
            {
                cout << "nnnnnnnnnnnnnnnnnnnnnnnnnnn" << endl;
            }
        }
    }

private:
    list<int> msgRecvQueue;
    mutex myMutex1;
    mutex myMutex2;
};

int main()
{
    A myobja;
    mutex myMutex;
    thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
    thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
    myOutMsgObj.join();
    myInMsgObj.join();
    return 0;
}

 9. unique_lock()取代lock_guard()

unique_lock是个类模板,一般推荐使用lock_guard()。

unique_lock比lock_guard灵活很多(多出来很多用法),但效率差一点,占用内存多。
unique_lock<mutex> myUniLock(myMutex);

9.1.unique_lock的第二个参数
9.1.1 std::adopt_lock:

表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了。
前提:必须提前lock;
lock_guard中也可以用这个参数


9.1.2 std::try_to_lock:

尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里;
使用try_to_lock的原因是防止其他的线程锁定mutex太长时间,导致本线程一直阻塞在lock这个地方
前提:不能提前lock();
owns_locks()方法判断是否拿到锁,如拿到返回true。


9.1.3 std::defer_lock:

如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex
不给它加锁的目的是以后可以调用unique_lock的一些方法
前提:不能提前lock


9.2.unique_lock的成员函数(前三个与std::defer_lock联合使用)
9.2.1 lock():加锁。不用自己unlock();

9.2.2 unlock():解锁。
因为一些非共享代码要处理,可以暂时先unlock(),用其他线程把它们处理了,处理完后再lock()。

9.2.3 try_lock():尝试给互斥量加锁
如果拿不到锁,返回false,否则返回true。

9.2.4 release():

  • unique_lock<mutex>

myUniLock(myMutex);相当于把myMutex和myUniLock绑定在了一起,release()就是解除绑定,返回它所管理的mutex对象的指针,并释放所有权

  • mutex* ptx =myUniLock.release();所有权由ptx接管,如果原来mutex对象处理加锁状态,就需要ptx在以后进行解锁了。

lock的代码段越少,执行越快,整个程序的运行效率越高。
a.锁住的代码少,叫做粒度细,执行效率高;
b.锁住的代码多,叫做粒度粗,执行效率低;

内容来源于bilibilic++11并发与多线程视频课程这个视频 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Windows环境下的多线程编程(上)C++ 的相关文章

  • std::atomic_thread_fence

    在原子变量的存取上应用不同的memory order可以实现不同的内存序来达到数据同步的目的 xff0c 而在C 43 43 11及之后的标准里 xff0c 除了利用原子操作指定内存序 xff0c 还定义了单独使用 内存栅栏 xff08 s
  • 【数据结构】【期末复习】知识点总结

    算法 线性表 概念明晰 xff1a 随机存取 顺序存取 随机存储和顺序存储 随机存取 顺序存取 随机存储和顺序存储这四个概念是完全不一样的 xff0c 切不可将之混淆 很多人包括我可能认为随机存取就是随机存储 xff0c 顺序存取就是顺序存
  • 【单片机学习】51单片机【定时/计数器】,详细介绍

    51单片机学习 一 先知先会1 CPU时序的有关知识1 1 周期换算2 在学习定时器之前需要明白的3 定时 计数器的工作原理4 51单片机定时器结构 二 定时 计数器的控制1 工作方式寄存器TMOD2 控制寄存器TCON3 定时 计数器的工
  • 【跟着江科大学Stm32】GPIO_LED_流水灯_蜂鸣器

    只要坚持下来了 xff0c 一定会有收获 xff01 一 LED闪烁 span class token macro property span class token directive hash span span class token
  • STM32F103C8T6 PWM驱动舵机(SG90)

    小知识 xff1a 同一个定时器 xff0c 不同通道输出不同输出PWM的特点 对于同一个定时器的不同通道输出PWM xff0c 因为它们是共用一个计数器的 xff0c 所以频率必须一样 xff0c 而占空比由各自的CCR决定 xff0c
  • STM32 PWM周期与频率的计算

    文章目录 STM32 PWM周期与频率的计算频率的计算占空比的计算笔记仅供自学 xff0c 用来回看复习 xff0c 不一定适合你 xff0c 如有错误请指出 STM32 PWM周期与频率的计算 TIM TimeBaseInitTypeDe
  • STM32F103C8T6 实现舵机与电机的控制 2个定时器输出不同频率的PWM

    智能小家居 舵机开门 xff0c 电机做风扇 or 拉窗帘 呼吸灯做提示 xff0c 小OLED屏幕显示当前状态 文章目录 直接上代码main cpwm hpwm cservo hservo cmotor hmotor c笔记仅供自学 xf
  • 【学习记录】Tpro遥控器_暂时取消Tpro的控制权(简易)

    文章目录 按照如下图示配置1 设置好 96 逻辑开关 96 2 选择执行该 96 逻辑开关指令 96 的 96 通道 96 3 配置成功附 xff1a 继电器与R88的接法 按照如下图示配置 1 设置好逻辑开关 2 选择执行该逻辑开关指令的
  • 【Linux】gcc编译工具,断点的设置,gdb调试

    文章目录 注意1 在gcc编译过程中一定要加入选项 96 g 96 xff1b 2 只有在代码处于 96 运行 96 中在 96 暂停 96 状态时才能查看变量值 xff1b 3 设置断点后 xff0c 程序在指定行之前停止 总结主要内容1
  • 【Linux】信号量操作函数

    文章目录 二 实验原理1 semget 函数函数作用 xff1a 参数意义 xff1a 例子 xff1a 2 semop 函数函数作用 xff1a 参数意义 xff1a struct sembuf 结构体定义如下例子 xff1a 再来个完整
  • 【Linux】# 2022 Linux 笔试主要内容 MJ_University

    2022 Linux 笔试主要内容 看前须知道 带 的都是老师复习课上提到的内容 xff0c 但不代表说一定会考哦 xff01 选择题 xff08 20分 xff09 填空题 xff08 20分 xff09 判断题 xff08 10分 xf
  • Ubuntu18.04安装AX210驱动

    Linux Support for Intel Wireless Adapters 从官网可以看到AX210支持的内核版本是5 10 43 如果要在低于5 10的内核版本上安装AX210的驱动的话 xff0c 需要安装以下方法操作 xff1
  • 【Linux】实验四 进程信号通信

    文章目录 一 实验目的二 实验内容三 实验原理1 信号3 1 1 信号的基本概念3 1 2 信号的发送 2 所涉及的系统函数调用3 2 1 fork 3 2 2 kill This is my question 3 2 3 signal 代
  • 报错.SO文件找不到

    此报错大多数可以通过设置环境变量解决 原因 xff1a 当执行函数动态链接 so时 xff0c 此文件不在缺省目录下 lib and usr lib 里 缺省 61 61 默认 解决方案 xff1a 1 find name so文件名 2
  • 头文件(.h) 和实现文件(.cpp)区别

    简单讲 xff0c 一个Package就是由同名的 h和 cpp文件组成 当然可以少其中任意一个文件 xff1a 只有 h文件的Package可以是接口或模板 template 的定义 xff1b 只有 cpp文件的Package可以是一个
  • 坚持学习100天:STL(头大的英文,一个模板库,非得写得我看不懂)

    前言 Hello 我是修齊 学习C 43 43 的第一百零一十八天 18是个美好的数字 xff0c 18岁刚刚好 xff0c 28岁也要用心学习 在这里记录一些学习的东西和学习的心情 内容主要是一些自己学习整理的小笔记 一 指针与引用 1
  • Ubuntu20.04以及ROS系统的安装(避坑指南)

    一 Ubuntu20 04的安装 暑期在我导那里确认好研究方向后 xff0c 一个人摸爬滚打走来确实走了不少弯路 xff0c 遂决定写下这篇文章来帮助有需要的朋友来避坑 本次安装是通过VMware来创建一台虚拟机进行后续操作 xff0c 之
  • Rplidar A2单线雷达实现Cartographer建图

    首先安装rplidar2 xff0c 在工作空间src目录下git clone xff0c catkin make编译后即可使用了 git clone https github com robopeak rplidar ros git 在使
  • 解决Ubuntu非root用户登录时,无法打开Firefox浏览器

    在使用Ubuntu操作系统时 xff0c 创建了一个普通用户 xff0c 登录该用户后发现无法打开Firefox浏览器 xff0c 进行如下步骤可解决问题 提示 Your Firefox profile cannot be loaded I
  • 【C语言】 链表 超详细解析

    目录 一 xff1a 静态存储和动态存储 二 xff1a 存储类别 三 xff1a malloc函数 四 xff1a free函数 五 xff1a 内存初始化函数memset 六 xff1a calloc函数 七 xff1a realloc

随机推荐

  • 2.stm32freeRTOS---创建一个任务

    文章目录 前言一 创建一个任务需要注意哪些 xff1f 1 任务创建函数xTaskCreate2 代码分析 二 动态 静态分配1 动态分配2 静态分配 xff1a 三 删除任务四 一个函数创建两个任务总结 前言 上一次是初步接触实时系统 x
  • 世界上最详细的Linux C udp实现文件传输

    最重要的是掌握UDP的基本概念和使用 UDP h span class token macro property span class token directive hash span span class token directive
  • LidarPointCloud保存PCD文件的方法

    在牵扯到Lidar点云数据相关的开发时 难免需要将LidarPointCloud数据转为PCD文件 来查看点云效果 本文介绍两种办法 1 写文件的方法 根据PCD文件的协议 本样例中使用的数据类型为ara lidar LidarPointC
  • SSD算法解析

    目标检测算法主要分为两类 xff1a Two stage方法 xff1a 如R CNN系列算法 xff0c 主要思路就是通过Selective Search或者CNN网络产生一系列的稀疏矩阵的候选区域 xff0c 然后对这些候选区域进行分类
  • Oracle获取日期&天数

    一 获取当年所有日期 span class token comment select from span span class token keyword select span trunc span class token punctua
  • Oracle where if

    一 where case when Oracle where不能如其他sql直接添加if逻辑 只能使用case when span class token keyword select span span class token opera
  • ASP.net GridView控件(删除/更新功能)

    一 说明 部分代码的运用放在以往的教程中 本部分只讲解删除 更新功能 二 前端 我们在其控件上添加事件 红色为行删除事件 绿色为行更新事件 双击后 即可在后台自动生成对应的方法体 其代码显示 lt 64 Page Language 61 3
  • ASP.net 简单登录界面

    一 说明 此文是小白在学习张晨光老师的视频教学 lt lt Asp Net WEB服务器编程技术 gt gt 中做的学习笔记 一些知识点也是跟着教程走的 大家也可以去老师的主页去学习 谢谢大家 这一篇要练习的是 如下课程的代码 新建项目 因
  • ASP.net 简单注册界面

    一 说明 此文是小白在学习张晨光老师的视频教学 lt lt Asp Net WEB服务器编程技术 gt gt 中做的学习笔记 一些知识点也是跟着教程走的 大家也可以去老师的主页去学习 谢谢大家 这一篇要练习的是 如下课程的代码 先新建img
  • oracle 一行转多行+多行转一行

    1 说明 在一行转多行时 我们多半将一张维护表分成单列的维护数据 然后再进行汇总 关联 这样能避免一些不必要的错误 一个table中 只有要拆分的数据和主键 如果要拆分多行 即将他们拆分为不同的table 2 简单的拆分 此语句是以逗号拆分
  • [vue element-ui]JAVA POST请求+eclipse创建项目

    01 前端 span class token doctype span class token punctuation lt span span class token doctype tag DOCTYPE span span class
  • [JAVA REST]REST请求

    java rest rest请求 01 说明02 前端AJAX优化03 后台的优化04 数据的处理 01 说明 本系列是对 阿发你好 JAVA教程做的个人笔记总结 如果小伙伴们有兴趣 请移步至阿发你好官网JAVA教程 保姆级视频教学 您值得
  • [Oracle]去除某行,单列重复的数据

    Oracle 去除某行 单列重复的数据 01 说明02 添加辅助列03 优先级排序04 去除重复项05 批量删除 01 说明 因为实在找不到可以模拟该方法的案例 就简单的说一下大概的数据和处理逻辑 小伙伴们懂这个逻辑就行 到实战里活学活用
  • 通过API获取rostopic list数据

    当然在终端上执行 rostopic list 会得到当前Master发布的话题信息 这就不说了 如图 那如何通过API获取rostopic list数据呢 先看效果 前提rosmaster已运行 ui部分用到了qt 相关的代码如下 cons
  • ROS学习(四)发布者与订阅者

    目录 一 发布者与订阅者通讯关系 二 发布者 1 一般创建步骤 2 配置CMakeLists txt中的编译规则 3 编译 4 设置环境变量 5 运行发布者 三 订阅者 1一般创建步骤 2 在CMakeLists txt中配置 xff0c
  • Could not find a package configuration file provided by“xxx“

    项目场景 xff1a 编译ros功能包的报错 问题描述 只要错误是 Could not find a package configuration file provided by xxx 原因分析 xff1a ROS找不到 xxx 提供的包
  • 猜数小游戏C++

    游戏简述 xff1a 一共有5组人 xff0c 每组有6个人吃点心 xff0c 每组总共有三十个点心 xff0c 先让玩家判断一共有几组 xff0c 回答正确则继续 xff0c 回答错误则继续猜 xff0c 一共有5次机会 猜不对游戏结束
  • Python画圣诞树和烟花源代码

    最近一直想让女朋友开心开心 xff0c 眼看就到圣诞了 xff0c 就想着来个不一样的 xff0c 给她画个圣诞树玩一玩 xff0c 也算是自己亲手做的 xff0c 用了心思了 看了关于画圣诞树的很多博客 xff0c 人才确实很多啊 xff
  • 学校食堂简易点餐管理系统(含用户登录且密码隐藏)C++

    系统运行步骤陈述 xff1a 运行程序进入用户登陆界面 输入账户及密码如果账户以及密码输入正确则进入系统 xff0c 显示登陆成功紧接着以下 须 按照指示输入 xff0c 所输入字母不区分大小写 进入系统后便可看见菜单选项 xff0c a
  • Windows环境下的多线程编程(上)C++

    1 为什么要用多线程 任务分解 xff1a 耗时的操作 xff0c 任务分解 xff0c 实时响应 数据分解 xff1a 充分利用多核CPU处理数据 数据流分解 xff1a 读写分流 xff0c 解耦合设计 2 并发 xff0c 进程 xf