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

目录

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.rhkb.cn/news/107356.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络】IP网络层和数据链路层

IP协议详解 1.概念 1.1 四层模型 应用层&#xff1a;解决如何传输数据&#xff08;依照什么格式/协议处理数据&#xff09;的问题传输层&#xff1a;解决可靠性问题网络层&#xff1a;数据往哪里传&#xff0c;怎么找到目标主机数据链路层&#xff08;物理层&#xff09;&…

Windows商店引入SUSE Linux Enterprise Server和openSUSE Leap

在上个月的Build 2017开发者大会上&#xff0c;微软宣布将SUSE&#xff0c;Ubuntu和Fedora引入Windows 商店&#xff0c;反应出微软对开放源码社区的更多承诺。 该公司去年以铂金会员身份加入Linux基金会。现在&#xff0c;微软针对内测者的Windows商店已经开始提供 部分Linux发…

react +Antd Cascader级联选择使用接口数据渲染

1获取接口数据并将数据转换成树形数组 useEffect(() > {axios.get(/接口数据, {params: {“请求参数”},}).then((res) > {console.log(res);const getTreeData (treeData, pid) > {// 把数据转化为树型结构let tree [];let currentParentId pid || 0;for (let i …

大数据扫盲(2): 数据分析BI与ETL的紧密关系——ETL是成功BI的先决条件

着业务的发展每个企业都将产生越来越多的数据&#xff0c;然后这些数据本身并不能直接带来洞察力并产生业务价值。为了释放数据的潜力&#xff0c;数据分析BI&#xff08;商业智能&#xff09;成为了现代企业不可或缺的一部分。然而&#xff0c;在数据分析的背后&#xff0c;有…

VUE笔记(九)vuex

一、vuex的简介 1、回顾组件之间的通讯 父组件向子组件通讯&#xff1a;通过props实现 子组件向父组件通讯&#xff1a;通过自定义事件($emit)方式来实现 兄弟组件之间的通讯&#xff1a;事件总线&#xff08;$eventBus&#xff09;、订阅与发布方式来实现 跨级组件的通讯…

word如何调整页码

文章目录 如何调整页码 如何调整页码 用 word 写报告的时候&#xff0c;经常遇到要求说是要从正文开始才显示页码&#xff0c;那如何实现呢 把鼠标放在我们正文的那一页的顶部&#xff0c;点击 布局 ,再点击分隔符&#xff0c;再点击连续 再点击编译页脚 选择你想要的页脚格式…

MySQL数据库基本操作

目录 一、数据库中常用的数据类型 二、常用命令与操作 1.DDL数据库定义语言 1、登录用户的数据库 2、查看当前服务器中的数据库 3、切换/进入数据库 并 查看数据库中包含的表 4、查看数据库中表的结构 5、创建数据库 7、展示创建数据表时的结构 8、创建表&#xff0c…

UML建模以及几种类图的理解

文章目录 前言1.用例与用例图1.1 参与者1.2 用例之间的关系1.3 用例图1.4 用例的描述 2.交互图2.1 顺序图2.2 协作图 3.类图和对象图3.1 关联关系3.2 聚合和组合3.3 泛化关系3.4 依赖关系 4.状态图与活动图4.1 状态图4.2 活动图 5.构件图 前言 UML通过图形化的表示机制从多个侧…

大语言模型之六- LLM之企业私有化部署

数据安全是每个公司不得不慎重对待的&#xff0c;为了提高生产力&#xff0c;降本增效又不得不接受新技术带来的工具&#xff0c;私有化部署对于公司还是非常有吸引力的。大语言模型这一工具结合公司的数据可以大大提高公司生产率。 私有化LLM需要处理的问题 企业内私有化LLM…

langchain ChatGPT AI私有知识库

企业知识库 原理就是把文档变为向量数据库&#xff0c;然后搜索向量数据库&#xff0c;把相似的数据和问题作为prompt&#xff0c; 输入到大模型&#xff0c;再利用GPT强大的自然语言处理、推理和分析等方面的能力将答案返回给用户 什么是langchain? langchain是一个强大的…

VScode代码自动补全提示

VScode代码自动补全提示 打开设置 搜索 Suggest:Snippets Prevent Quick Suggestions &#xff0c;去掉勾选 CtrlShiftP打开setting.json文件&#xff0c;添加以下代码 "editor.suggest.snippetsPreventQuickSuggestions": false,"editor.quickSuggestions…

机器学习概述

文章目录 机器学习应用背景数据挖掘个性化定制替代人力的软件应用 什么是机器学习示例 机器学习系统举例IBM Watson DeepQAIBM Watson技术需求相关技术 -- DeepQA 通用机器学习系统设计设计一个学习系统 1系统设计1 —— 用于训练的经验 设计学习系统 2系统设计2 —— 到底应该…

Spring Boot多环境指定yml或者properties

Spring Boot多环境指定yml或者properties 文章目录 Spring Boot多环境指定yml或者properties加载顺序配置指定某个yml 加载顺序 ● application-local.properties ● application.properties ● application-local.yml ● application.yml application.propertes server.port…

Tcp 协议的接口测试

首先明确 Tcp 的概念&#xff0c;针对 Tcp 协议进行接口测试&#xff0c;是指基于 Tcp 协议的上层协议比如 Http &#xff0c;串口&#xff0c;网口&#xff0c; Socket 等。这些协议与 Http 测试方法类似&#xff08;具体查看接口自动化测试章节&#xff09;&#xff0c;但在测…

Nuxt.js--》添加路由、视图和过渡效果

博主今天开设Nuxt.js专栏&#xff0c;带您深入探索 Nuxt.js 的精髓&#xff0c;学习如何利用其强大功能构建出色的前端应用程序。我们将探讨其核心特点、灵活的路由系统、优化技巧以及常见问题的解决方案。无论您是想了解 Nuxt.js 的基础知识&#xff0c;还是希望掌握进阶技巧&…

根据源码,模拟实现 RabbitMQ - 网络通讯设计,自定义应用层协议,实现 BrokerServer (8)

目录 一、网络通讯协议设计 1.1、交互模型 1.2、自定义应用层协议 1.2.1、请求和响应格式约定 ​编辑 1.2.2、参数说明 1.2.3、具体例子 1.2.4、特殊栗子 1.3、实现 BrokerServer 1.3.1、属性和构造 1.3.2、启动 BrokerServer 1.3.3、停止 BrokerServer 1.3.4、处…

Java小游戏

一、需求 二、思路一 HP当然是怪物的一个属性成员&#xff0c;而武器是角色的一个属性成员&#xff0c;类型可以使字符串&#xff0c;用于描述目前角色所装备的武器。角色类有一个攻击方法&#xff0c;以被攻击怪物为参数&#xff0c;当实施一次攻击时&#xff0c;攻击方法被调…

31、springboot 配置HTTP服务端口及如何通过WebServer实例动态获取项目中的HTTP端口

配置HTTP服务端口及如何通过WebServer实例动态获取项目中的HTTP端口 ★ 设置HTTP服务端口&#xff1a; - server.port或者SERVER_PORT环境变量——总结来说&#xff0c;其实就是要配置server.port外部配置属性。▲ 同样遵守如下优先级&#xff1a; 这些都是外部配置源&#x…

Android——基本控件下(十七)

1. 文本切换&#xff1a;TextSwitcher 1.1 知识点 &#xff08;1&#xff09;理解TextSwitcher和ViewFactory的使用。 1.2 具体内容 范例&#xff1a;切换显示当前时间 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools&…