当前位置: 首页 > news >正文

c++并发与多线程

c++并发与多线程

子线程结束,主线程不能结束,否则会出错,和java不一样。

可以用join的方式让主线程等待子线程执行结束。

quickStart

线程相关头文件 #include <thread>

使用全局函数构造一个线程对象

#include <iostream>
#include <thread>
#include <fstream>void myPrint() {ofstream ofs;ofs.open("data.txt", ios_base::out);for (size_t i = 0; !flag; i++){ofs << "new thread " <<i <<endl;}ofs.close(); }int main()
{thread thread1(myPrint);thread1.join();//主线程等待thread1执行结束for (size_t i = 0; i < 100; i++){cout << "main thread " << i << endl;}
}

这里用join让主线程等待子线程结束,再执行后面的代码。

创建thread对象后,线程即可启动。

线程的执行方式join和detach

join

thread thread1(myPrint);
thread1.join();//主线程等待thread1执行结束

调用子线程join方法后,父线程会阻塞,等待子线程执行结束后再执行。

detach

thread1.detach();

调用子线程detach方法后,子线程会和父线程分离,子线程父线程同时进行,主线程可以不用等待其他线程结束就可以正常结束。

ℹ️需要注意的问题:

  1. 当子线程中有主线程对象的引用时,如果主线程先于子线程结束,这些引用的对象就会被释放,导致子线程中的引用出错
  2. 主线程中用来构造子线程的对象(对象A),会被复制到子线程中,因此,主线程结束释放对象A后不会影响子线程的执行

线程的构造方式

全局函数

#include <iostream>
#include <thread>
#include <fstream>void myPrint() {ofstream ofs;ofs.open("data.txt", ios_base::out);for (size_t i = 0; !flag; i++){ofs << "new thread " <<i <<endl;}ofs.close(); }int main()
{thread thread1(myPrint);thread1.join();//主线程等待thread1执行结束for (size_t i = 0; i < 100; i++){cout << "main thread " << i << endl;}
}

对象

class ThreadJob 
{int count;public:ThreadJob(int n):count(n) {cout << "MyThread()" << endl;}ThreadJob(const ThreadJob& mt):count(mt.count) {cout << "MyThread(const MyThread&)" << endl;}//重载括号运算符void operator()() {cout << "MyThread work" << endl;}
};int main()
{//构造方式2:使用可执行对象构造线程//ThreadJob 对象会被复制到线程中去,因此主线程结束后myThread对象被释放不会影响线程thread2的执行ThreadJob job(5);thread thread2(job);if (thread2.joinable()){thread2.join();}for (size_t i = 0; i < 100; i++){cout << "main thread " << i << endl;}return 0;
}

lambda表达式

int main()
{/*for (size_t i = 0; i < 100; i++){cout << "main thread " << i << endl;}*///构造方式3:lambdaauto myLamdaThreadJob=[]{cout << "labmda" << endl;};thread thread3(myLamdaThreadJob);thread3.join();flag = true;return 0;
}

c++中lambda表达式的内容可以参考

https://docs.microsoft.com/zh-cn/cpp/cpp/lambda-expressions-in-cpp?view=msvc-160

std::this_thread

指代当前线程对象

线程ID

使用下面方法可以获取线程id

std::this_thread::get_id()

线程休眠

std::chrono::milliseconds dura(200);
std::this_thread::sleep_for(dura);

线程传递参数

传递引用

示例

使用函数指针构造线程,函数传递两个参数

class ThreadJob 
{int count;public:ThreadJob(int n):count(n) {cout << "ThreadJob() threadId="<<this_thread::get_id() << endl;}ThreadJob(const ThreadJob& mt):count(mt.count) {cout << "ThreadJob(const ThreadJob&) threadId=" << this_thread::get_id() << endl;}~ThreadJob(){cout << "~ThreadJob() threadId=" << this_thread::get_id() << this<< endl;}};void myPrint2( int i, const ThreadJob& job) {cout << &job << endl;
}
int main()
{cout << "main threadId=" << this_thread::get_id() << endl;ThreadJob job(10);thread thread1(myPrint2,10,ref(job));thread1.join();return 0;
}

注意:

void myPrint2( int i, const ThreadJob& job) {cout << &job << endl;
}

这个函数中的引用参数必须得用const修饰。

  • 构造线程时,如果参数会发生隐式转换,则隐式转化发生在子线程中
 thread thread1(myPrint2,10,20); //这里把20使用ThreadJob(int)隐式转换成ThreadJob对象
  • 这会导致在主线程释放了局部变量后,子线程中的隐式转换还没有完成,从而出错。
  • 可以使用匿名对象的方式显示构造对象
thread thread1(myPrint2,10,ThreadJob(20));//匿名对象的构建发生在主线程中
  • 匿名对象构建完成之后,不管线程回调函数参数中是否是引用,都会将主线程中传入的参数对象复制一份给子线程。如果回调函数参数不是引用,则对象复制会发生两次
  • 可以使用std::ref(obj)或者有些情况下也可以用&obj方式传递主线程对象的真正引用,
 thread thread1(myPrint2,10,std::ref(job));

传递智能指针

示例:传递独占式指针

void job1(unique_ptr<int> i) {}
int main()
{unique_ptr<int> i1(new int(100));thread thread4(job1,std::move(i1));thread4.join();
}

传递成员函数

示例

class ThreadJob 
{int count;public:ThreadJob(int n):count(n) {//....}void work(int n) {//.....}
};int main()
{ThreadJob job(10);thread thread2(&ThreadJob::work,job,10); //job对象会复制一份到子线程中thread2.join();
}

加锁

互斥量(mutex)、lock、unlock

mutex.lock和mutex.unlock必须成对使用

mutex需要包含头文件

#include <mutex>

示例代码:

#include <iostream>
#include <mutex>
#include<list>using namespace std;class MsgProcessor
{public:
void MsgProcessor::procMsg()
{for (size_t i = 0; i < 10000; i++){m_mutex.lock();if (!msgQueue.empty()){int msg = msgQueue.front();cout << "process msg:" << i << endl;msgQueue.pop_front();}else{cout << "start msgProc() but list is empty" << endl;}m_mutex.unlock();}
}
void MsgProcessor::receiveMsg()
{for (size_t i = 0; i < 1000; i++){cout << "receive msg:" << i << endl;m_mutex.lock(); //加锁msgQueue.push_back(i);m_mutex.unlock();//解锁}
}
private:std::list<int> msgQueue;//互斥量std::mutex m_mutex;
};int main()
{MsgProcessor msgProcessor;thread threadRecMsg(&MsgProcessor::receiveMsg, &msgProcessor);thread threadProcMsg1(&MsgProcessor::procMsg, &msgProcessor);thread threadProcMsg2(&MsgProcessor::procMsg, &msgProcessor);threadRecMsg.join();threadProcMsg1.join();threadProcMsg2.join();return 0;
}

std::lock_guard类模板

使用lock_guard模板类对象可以自动加锁和释放锁,

加锁范围为从对象声明构造开始到作用范围结束后对象析构

void MsgProcessor::procMsg()
{for (size_t i = 0; i < 10000; i++){//lock_guard 对象在构造时会调用mutex对象的lock方法,析构时会调用mutex的unlock方法std::lock_guard<std::mutex> locker(m_mutex);//.....}
}

std::lock函数模板

  • std::lock函数模板可以一次给两个或者两个以上的互斥量
  • 不存在因为锁的顺序导致死锁的问题
    • 原因:等待所有互斥量全都锁住才能完成上锁
  • 加锁之后需要手动释放锁

示例:

std::lock(m_mutex, m_mutex2);
if (!msgQueue.empty())
{int msg = msgQueue.front();cout << "process msg:" << i << endl;msgQueue.pop_front();
}
m_mutex.unlock();
m_mutex2.unlock();

使用std::lock之后需要手动释放锁,可以使用lock_guard的特性实现自动释放锁。

std::lock(m_mutex, m_mutex2);std::lock_guard<mutex> guard1(m_mutex, std::adopt_lock);
//使用lock_guard(mutex)会默认调用mutex的lock方法加锁,而std::lock方法中已经给互斥量加过锁,
//因此,这里必须在构造时传入std::adopt_lock这个参数
std::lock_guard<mutex> guard2(m_mutex2, std::adopt_lock);if (!msgQueue.empty())
{int msg = msgQueue.front();cout << "process msg:" << i << endl;msgQueue.pop_front();
}

unique_lock类模板

  • lock_guard简化了mutexlockunlock管理

  • unique_locklock_guard更灵活,但是效率较低

  • unique_lock可以完全取代lock_guard

std::unique_lock<std::mutex> lock1(m_mutex);if (!msgQueue.empty())
{int msg = msgQueue.front();cout << "process msg:" << i << endl;msgQueue.pop_front();
}

构造函数

  • std::adopte_lock,表示互斥量已经被加锁
//std::adopte_lock
m_mutex.lock();
std::unique_lock<std::mutex> lock1(m_mutex,std::adopt_lock);
  • std::try_to_lock
    • 尝试去锁定mutex,如果没有锁定成功,则会立即返回,并不会产生阻塞
    • 使用前互斥量不能加锁
std::unique_lock<mutex> lock1(m_mutex,std::try_to_lock);
if (lock1.owns_lock())
{cout << "receive msg:" << endl;
}
else
{cout << "接受消息线程没有拿到消息队列锁,跳过" << endl;
}
  • std::defer_lock
    • 使用时互斥量不能加锁,否则会报异常
    • 这个参数表示初始化一个没有加锁的互斥量
std::unique_lock<std::mutex> lock1(m_mutex, std::defer_lock);
lock1.lock();

重要成员函数

lock

  • 使用lock()加锁之后可以自动释放锁
std::unique_lock<std::mutex> lock1(m_mutex, std::defer_lock);
lock1.lock();

unlock()

  • 使用unlock()释放锁
std::unique_lock<std::mutex> lock1(m_mutex, std::defer_lock);
lock1.lock();
//...
lock1.unlock();
//...
lock1.lock();
//...
lock1.unlock();

try_lock()

  • 返回值
    • true: 拿到锁
    • false: 没有拿到锁
std::unique_lock<std::mutex> lock1(m_mutex, std::defer_lock);if (lock1.try_lock()) 
{//拿到锁
}
else
{//没有拿到锁
}

release()

返回unique_lock对象所管理mutex对象的指针,并释放所有权

std::unique_lock<std::mutex> lock1(m_mutex, std::defer_lock);
lock1.lock();
std::mutex *mutex_ptr = lock1.release();
mutex_ptr->unlock();

unique_lock所有权的传递

  • 通常unique_lock需要管理一个 mutex对象
  • 所有权可以传递,但是不能复制
std::unique_lock<std::mutex> lock1(m_mutex, std::defer_lock);
lock1.lock();
//所有权的传递
std::unique_lock<std::mutex> lock2(std::move(lock1));

std::call_once()

  • std::call_once是c++11引入的函数,能够保证传入的函数只被调用一次,比使用mutex消耗的资源更少
  • std::call_once需要和 std::once_flag结合使用。

单例模式示例

实现一个日志管理器LogManager

LogManager.h

#pragma once
#include <mutex>
using namespace std;
class LogManager
{
private:static LogManager* manager;static mutex *lock1;LogManager();//用于释放单例对象class GC {public:~GC();};
public:static LogManager* getInstance();void test();};

LogManager.cpp

#include "LogManager.h"
#include <iostream>LogManager::LogManager()
{
}LogManager* LogManager::getInstance()
{//这里使用两段锁的方式来进行单例对象的初始化if (manager==NULL){LogManager::lock1->lock();if (manager==NULL){manager = new LogManager();static GC gc;}LogManager::lock1->unlock();}return manager;
}void LogManager::test()
{std::cout << "test" << std::endl;
}LogManager::GC::~GC() {if (manager){delete manager;manager = NULL;}
}//类的静态成员变量要在类声明的外部初始化
LogManager* LogManager::manager = NULL;
mutex* LogManager::lock1 = new mutex();

main.cpp

#include <iostream>
#include "LogManager.h"int main()
{std::cout << "Hello World!\n";LogManager* manager1 = LogManager::getInstance();LogManager* manager2 = LogManager::getInstance();manager1->test();
}

call_once在单例模式中的应用

LogManager.h

#pragma once
#include <mutex>
using namespace std;
class LogManager
{
private:static LogManager* manager;//static mutex *lock1;//std::call_once需要的标记static std::once_flag* flag;LogManager();static void createInstance();class GC {public:~GC();};
public:static LogManager* getInstance();void test();};

LogManager.cpp

#include "LogManager.h"
#include <iostream>LogManager::LogManager()
{
}void LogManager::createInstance()
{manager = new LogManager();static GC gc;
}LogManager* LogManager::getInstance()
{std::call_once(*flag, createInstance);return manager;
}void LogManager::test()
{std::cout << "test" << std::endl;
}LogManager::GC::~GC() {if (manager){delete manager;manager = NULL;}
}
LogManager* LogManager::manager = NULL;
mutex* LogManager::lock1 = new mutex();
std::once_flag* LogManager::flag = new std::once_flag();

std::condition_variable、wait、notify

  • condition_variable.wait()方法可以让一个线程等待
  • condition_variable.notify_one()和notify_all()可以唤醒等待的线程

示例

//定义条件变量
std::condition_variable cv;//使用wait
std::unique_lock<std::mutex> lock(m_mutex);cv.wait(lock, [this] {if (msgQueue.empty()){cout << "消息队列为空,处理线程等待" << endl;return false;}return true;
});//使用notify
std::unique_lock<mutex> lock1(m_mutex,std::try_to_lock);
if (lock1.owns_lock())
{cout << "receive msg:" << i << endl;msgQueue.push_back(i);//唤醒正在wait的线程cv.notify_all();
}
  • std::conditioan_variable.wait()第二个参数lambda表达式返回bool
    • true,wait 直接返回,程序继续执行
    • false, wait 将解锁互斥量,并且让线程等待被唤醒
  • condition.wait()没有第二个参数,和第二个参数直接返回false效果一样,wait将解锁互斥量,并让线程等待
  • wait中的线程可以通过 notify_one()唤醒,唤醒后会首先尝试继续获取互斥量的的锁
  • 获取到锁之后,如果wait时有第二个参数,则会继续判断第二个参数的返回值,
    • false重新释放锁并等待,true继续执行后续语句

notify_one和notify_all

  • notify_one 可以从等待中的线程中唤醒一个
  • notify_all可以将所有等待中的线程唤醒

std::async,std::future

  • std::async 是个函数模板,用来启动一个异步任务,启动起来的异步任务会返回一个 std::future对象,

    • 异步任务可以通过std::future对象返回一个结果,使用future.get()获取结果
    • 也可以使用future.wait等待线程结束,不取得结果
    • 如果不显式调用 future.get或者 future.wait,程序结束时也会等待线程结束
  • 需要的头文件 future

    示例

int sum(int* data, int n) {cout << "thread id=" << this_thread::get_id() << " start work" << endl;int result = 0;for (size_t i = 0; i < n; i++){result += data[i];}cout << "thread id=" << this_thread::get_id() << " finish work" << endl;return result;
}const int length = 10;int main()
{srand((unsigned)time(0));int* data;data = new int[length];for (size_t i = 0; i < length; i++){data[i] = rand() % 100;}future<int> Future1 = async(sum, data, length);//线程立即开始执行future<int> Future2 = async(sum, data, length);//get只能调用一次int sum1 = Future1.get();//wait等待线程执行结束,不拿到返回结果Future2.wait();}

线程控制参数

future<int> Future1 = async(std::launch::deferred, sum, data, length);
future<int> Future2 = async(std::launch::async, sum, data, length);
  • 使用std::async()是可以传入 std::launch枚举参数
    • std::launch::deferred
      • 线程入口函数调用被延迟到 future的wait或者 get方法调用时才执行
      • 两个方法都没有调用时,线程不执行
      • 这种情况下,代码实际上是在调用线程中执行的
    • std::launch::async
      • 强制创建新的线程(async并不是所有情况下都创建新的线程)
    • launch::deferred和launch::async可以同时使用
    • 不传入参数时,默认为 async|deffered,由系统决定是否创建新的线程
std::async(std::launch::async|std::launch::deferred, sum, data, length);

std::future

  • std::future的get函数会进行结果的转移,所以只能使用一次,如果需要多次获取future的结果,可以使用shared_future

  • future_status

future.wait_for()可以获取线程执行状态,返回值是 std::future_status

enum class future_status { // names for timed wait function returnsready,timeout,deferred
};

示例:

future<int> future2 = async(std::launch::async, sum, data, length);//future_status//这里等待2000ms获取future的状态
std::future_status status = future2.wait_for(chrono::milliseconds(2000));
if (status==std::future_status::timeout)
{cout << "timeout";
} 
else if (status == std::future_status::ready)
{future1.get();cout << "ready";
}
else if (status == std::future_status::deferred)
{cout << "deferred";
}cout << endl;
  • future.valid()
    • 判断future是否有效,一个future被调用过get方法或者自身被构造为shared_future之后,会发生持有变量的转移,导致valid返回false(0)
future<int> future2 = async(std::launch::async, sum, data, length);
cout << "future2.valid()=" << future2.valid() << endl;
std::shared_future<int> shared_future1(future2.share());
cout <<"future2.valid()=" << future2.valid() << endl;

std::shared_future

  • 共享future,get方法会把线程返回的结果复制并返回,因此可以多次调用get方法
future<int> future2 = async(std::launch::async, sum, data, length);//构造shared_future
//方式1
std::shared_future<int> shared_future1(std::move(future2));
//方式2
std::shared_future<int> shared_future1(future2.share());//多次调用不会报错
shared_future1.get();
shared_future1.get();
shared_future1.get();
  • 可以直接使用future来构造shared_future
//1
std::packaged_task<int(int*, int)> pt(sum);
std::shared_future<int> sfuture1(pt.get_future());//2
std::shared_future<int> sfuture2(std::async(sum,data,length));

async和thread的区别

  • async创建异步任务时,有的时候并不创建新的线程
  • thread一定会创建新的线程,创建失败程序会出错
  • async在没有参数指定必须创建新的线程,无法创建新的线程时,就不会创建新的线程,而是在调用它的线程上运行

std::packaged_task

  • std::packaged_task是一个类模板,模板参数是各种可调用对象
  • 通过std::packaged_task可以把各种可调用对象包装起来,方便将来作为线程入口参数
  • 也可以直接调用,但是这种情况下没有新的线程
  • 头文件 <future>
//创建 packaged_task对象
std::packaged_task<int(int*,int)> m_task(sum);//===使用packageed_task创建线程==
std::thread t1(std::ref(m_task), data,length);
t1.join(); 
std::future<int> res = m_task.get_future(); //前面的t1已经开始执行,主线程不会等待子线程结束后再结束,所以必须先join再获取结果//==直接调用packaged_task对象==
m_task(data, length);
future<int> res2 = m_task.get_future();
res2.get();

std::promise

  • 类模板,能够在某个线程中给它赋值,在其他线程中取值
#include <iostream>
#include <thread>
#include <future>using namespace std;void myThread(std::promise<int>& tmp, int param1) 
{chrono::milliseconds dura(1000);this_thread::sleep_for(dura);param1++;tmp.set_value(param1); //给promise对象赋值return;}int main()
{std::promise<int> Promise;thread t1(myThread, std::ref(Promise), 10);t1.join();future<int> Future = Promise.get_future(); //从promise对象获取future,只能操作一次auto result = Future.get();cout << result << endl;
}

原子操作

原子操作示例

一些操作,例如加法,在计算机内部执行时会被分解成很多步骤,如果在执行时发生线程切换,会导致一次加法没有做完就切换到其他线程

#include <iostream>
#include <thread>using namespace std;
int m_count = 0;void add_self()
{for (size_t i = 0; i < 1000000; i++){m_count++;}}int main()
{thread t1(add_self);thread t2(add_self);t1.join();t2.join();//m_count的结果有可能不是2000000cout << "m_count=" << m_count << endl;
}

解决方法1,使用互斥量

int m_count = 0;
mutex m1;void add_self()
{for (size_t i = 0; i < 1000000; i++){m1.lock();m_count++;m1.unlock();}
}

解决方法1,使用原子操作

概述

  • 原子操作是不使用互斥量加锁就可以实现 程序片段不会被打断的多线程并发技术
  • 比互斥量效率更高一点
  • 原子操作一般都是针对一个变量,而互斥量是作用在代码片段中
// 也可以使用 std::atomic_int来代替 std::atomic<int>
std::atomic<int> m_count = 0;void add_self()
{for (size_t i = 0; i < 1000000; i++){ //这是一个原子操作,不会被线程切换打断m_count++;}
}

常见原子操作

  • ++,–,+=,-=,&=,等运算是原子操作
  • v=v+1这类不是原子操作

注意事项

不能给原子变量进行拷贝构造,例如以下代码是错误的:

std::atomic<int> m_count = 0;
std::atomic<int> b = m_count;
  • 原子方式读取 atomic.load()

可以使用原子变量的load方法以原子操作的方式读取变量值

std::atomic<int> m_count = 0;
std::atomic<int> b(m_count.load());
  • 原子方式写入 atomic.store()
std::atomic<int> m_count = 0;
m_count.store(50);

recursive_mutex 递归的独占互斥量

  • mutex在加锁前锁必须处于没有加锁的状态下,即不能在同一个线程中多次加锁

  • recursive_mutex允许多次加锁

  • 递归互斥量效率比互斥量低

recursive_mutex lock1;
int main()
{lock1.lock();lock1.lock();std::cout << "Hello World!\n";lock1.unlock();lock1.unlock();   
}

带超时功能的互斥量

获取超时互斥量的锁时,如果一段时间内没有获取到锁,程序会取消阻塞

mutex和recursive_mutex分别有对应的超时互斥量timed_mutexrecursive_timed_mutex

std::timed_mutex timeLock;
std::recursive_timed_mutex reTimeLock;
  • 重要方法
    • try_lock_for
    • try_lock
    • try_lock_until
timeLock.try_lock_for(4000ms);lock.try_lock_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1000));

示例

int main()
{std::timed_mutex timeLock;//这里用lambda表达式构造了一个线程thread t1([&timeLock]() {if (timeLock.try_lock_for(4000ms)){cout << "thread " << this_thread::get_id() << " 获取到锁" << endl;this_thread::sleep_for(2s);timeLock.unlock();cout << "thread " << this_thread::get_id() << "释放锁" << endl;}else{cout << "thread " << this_thread::get_id() << " 没有获取到锁" << endl;}});timeLock.lock();cout << "main thread " << this_thread::get_id() << " start work" << endl;this_thread::sleep_for(3s);cout<< "main thread " << this_thread::get_id() << " finish work" << endl;timeLock.unlock();t1.join();
}

相关文章:

c++并发与多线程

c并发与多线程 子线程结束&#xff0c;主线程不能结束&#xff0c;否则会出错&#xff0c;和java不一样。 可以用join的方式让主线程等待子线程执行结束。 quickStart 线程相关头文件 #include <thread> 使用全局函数构造一个线程对象 #include <iostream> #…...

Vinylsulfone PEG Biotin,Biotin-PEG-VS,生物素聚乙二醇乙烯砜,VS基团容易与游离巯基发生反应

●中文名&#xff1a;乙烯砜PEG生物素&#xff0c;生物素聚乙二醇乙烯砜 ●英文名&#xff1a;Vinylsulfone PEG Biotin, VS-PEG-Biotin&#xff0c;Vinyl sulfone-PEG-Biotin&#xff0c;Biotins-PEG-sulfone Vinyl●产品理化指标&#xff1a; CAS号&#xff1a;N/A 分子量&am…...

论文学习——Tune-A-Video

Tune-A-Video: One-Shot Tuning of Image Diffusion Models for Text-to-Video Generation Abstract 本文提出了一种方法&#xff0c;站在巨人的肩膀上——在大规模图像数据集上pretrain并表现良好的 text to image 生成模型——加入新结构并进行微调&#xff0c;训练出一套 …...

C++类与对象part1

目录 1.类的6个默认函数 2.构造函数&#xff08;相当于init&#xff09; 3.析构函数 &#xff08;相当于destroy&#xff09; 4.拷贝构造函数 赋值运算符重载 运算符重载 赋值运算符重载 引入&#xff1a; 你知道为什么cout可以自动识别类型吗&#xff1f; 其实cout是一…...

记一次抓取网页内容

已打码 // UserScript // name --------- // namespace http://tampermonkey.net/ // version 0.1 // description https://---------oups/{id}/topics?scopeall&count20&begin_time2022-09-01T00%3A00%3A00.000%2B0800&end_time2022-10-01T00%…...

parasoft帮助史密斯医疗通过测试驱动开发提供安全、高质量的医疗设备

parasoft是一家专门提供软件测试解决方案的公司&#xff0c;Parasoft通过其经过市场验证的自动化软件测试工具集成套件&#xff0c;帮助企业持续交付高质量的软件。Parasoft的技术支持嵌入式、企业和物联网市场&#xff0c;通过将静态代码分析和单元测试、Web UI和API测试等所有…...

SpringBoot整合Oauth2开放平台接口授权案例

<!-- SpringBoot整合Web组件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId>&l…...

Linux_创建用户

创建一个名为hello的用户&#xff0c;并指定/home/hello为根目录useradd -d /home/hello -m hello 设置密码 ,密码会输入两次&#xff0c;一次设置密码&#xff0c;一次确认密码&#xff0c;两次密码要输入的一样passwd hellouseradd的常用参数含义-d指定用户登入时的主目录&am…...

RDD(弹性分布式数据集)总结

文章目录一、设计背景二、RDD概念三、RDD特性四、RDD之间的依赖关系五、阶段的划分六、RDD运行过程七、RDD的实现一、设计背景 1.某些应用场景中&#xff0c;不同计算阶段之间会重用中间结果&#xff0c;即一个阶段的输出结果会作为下一个阶段的输入。如&#xff1a;迭代式算法…...

服务器版RstudioServer安装与配置详细教程

Docker部署Rstudio server 背景&#xff1a;如果您想在服务器上运行RstudioServer&#xff0c;可以按照如下方法进行操作&#xff0c;笔者测试时使用腾讯云服务器&#xff08;系统centos7&#xff09;&#xff0c;需要在管理员权限下运行 Rstudio 官方提供了使用不同 R 版本的 …...

如何在Java中将一个列表拆分为多个较小的列表

在Java中&#xff0c;有多种方法可以将一个列表拆分为多个较小的列表。在本文中&#xff0c;我们将介绍三种不同的方法来实现这一目标。 方法一&#xff1a;使用List.subList()方法 List接口提供了一个subList()方法&#xff0c;它可以用来获取列表中的一部分元素。我们可以使…...

TryHackMe-Inferno(boot2root)

Inferno 现实生活中的机器CTF。该机器被设计为现实生活&#xff08;也许不是&#xff1f;&#xff09;&#xff0c;非常适合刚开始渗透测试的新手 “在我们人生旅程的中途&#xff0c;我发现自己身处一片黑暗的森林中&#xff0c;因为直截了当的道路已经迷失了。我啊&#xf…...

微信原生开发中 JSON配置文件的作用 小程序中有几种JSON配制文件

关于json json是一种数据格式&#xff0c;在实际开发中&#xff0c;JSON总是以配制文件的形式出现&#xff0c;小程序与不例外&#xff0c;可对项目进行不同级别的配制。Q&#xff1a;小程序中有几种配制文件A:小程序中有四种配制文件分别是&#xff1a;project.config.json si…...

【python】为什么使用python Django开发网站这么火?

关注“测试开发自动化” 弓中皓&#xff0c;获取更多学习内容&#xff09; Django 是一个基于 Python 的 Web 开发框架&#xff0c;它提供了许多工具和功能&#xff0c;使开发者可以更快地构建 Web 应用程序。以下是 Django 开发中的一些重要知识点&#xff1a; MTV 模式&#…...

Java设计模式(五)—— 责任链模式

责任链模式定义如下&#xff1a;使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者与接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;知道有一个对象处理它为止。 适合使用责任链模式的情景如下&#xff1a; 有许多对…...

VMLogin:虚拟浏览器提供的那些亮眼的功能

像VMLogin这样的虚拟浏览器具有多种功能&#xff0c;如安全的浏览环境、可定制的设置、跨平台的兼容性、更快的浏览速度、广告拦截等等。 虚拟浏览器的不同功能可以为您做什么&#xff1f; 使用虚拟浏览器是浏览互联网和完成其他任务的安全方式&#xff0c;没有风险。您可以在…...

第一个错误的版本

题目 你是产品经理&#xff0c;目前正在带领一个团队开发新的产品。不幸的是&#xff0c;你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的&#xff0c;所以错误的版本之后的所有版本都是错的。 假设你有 n 个版本 [1, 2, …, n]&#xff0c;你想找出…...

2023爱分析·AIGC市场厂商评估报告:拓尔思

AIGC市场定义 市场定义&#xff1a; AIGC&#xff0c;指利用自然语言处理技术&#xff08;NLP&#xff09;、深度神经网络技术&#xff08;DNN&#xff09;等人工智能技术&#xff0c;基于与人类交互所确定的主题&#xff0c;由AI算法模型完全自主、自动生成内容&#xff0c;…...

MobTech|场景唤醒的实现

什么是场景唤醒&#xff1f; 场景唤醒是moblink的一项核心功能&#xff0c;可以实现从打开的Web页面&#xff0c;一键唤醒App&#xff0c;并恢复对应的场景。 场景是指用户在App内的某个特定页面或状态&#xff0c;比如商品详情页、活动页、个人主页等。每个场景都有一个唯一…...

不在路由器上做端口映射,如何访问局域网内网站

假设现在外网有一台ADSL直接拨号上网的电脑&#xff0c;所获得的是公网IP。然后它想访问局域网内的电脑上面的网站&#xff0c;那么就需要在路由器上做端口映射。在路由器上做端口映射的具体规则是&#xff1a;将所有发向自己端口的数据&#xff0c;都转发到内网的计算机。 访…...

ChatGPT 辅助科研写作

前言 总结一些在科研写作中 ChatGPT 的功能&#xff0c;以助力提升科研写作的效率。 文章目录前言一、ChatGPT 简介1. ChatGPT 普通版与 Plus 版的区别1&#xff09;普通账号2&#xff09;Plus账号二、New Bing 简介1. 快速通过申请三、辅助学术写作1. 改写论文表述2. 语言润色…...

MySQL最大建议行数 2000w,靠谱吗?

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址 1 背景 作为在后端圈开车的多年…...

【Tomcat 学习】

Tomcat 学习 笔记记录一、Tomcat1. Tomcat目录2. Tomcat启动3. Tomcat部署项目4. 解决Tomcat启动乱码问题5. JavaWeb项目创建部署6. 打war包发布项目7. Tomcat配置文件8. Tomcat配置虚拟目录(不用在webapps目录下)9. Tomcat配置虚拟主机10. 修改web项目默认加载资源路径一、Tom…...

重装系统如何做到三步装机

小白三步版在给电脑重装系统的过程中&#xff0c;它会提供系统备份、还原和重装等多种功能。下面也将介绍小白三步版的主要功能&#xff0c;以及使用技巧和注意事项。 主要功能 系统备份和还原&#xff1a;小白三步版可以帮助用户备份系统和数据&#xff0c;以防止重要数据丢失…...

蓝桥杯单片机第十一届省赛客观题(深夜学习——单片机)

第一场 &#xff08;1&#xff09;模电——》多级放大电路 阻容耦合&#xff0c;只通交流&#xff0c;不通直流。 变压器耦合&#xff0c;只通交流&#xff0c;不通直流。 光电耦合&#xff0c;主要是起隔离作用&#xff0c;更多的用在非线性的应用电路中 &#xff08;2&a…...

Pandas对Excel文件进行读取、增删、打开、保存等操作的代码实现

文章目录前言一、Pandas 的主要函数包括二、使用步骤1.简单示例2.保存Excel操作3.删除和添加数据4.添加新的表单总结前言 Pandas 是一种基于 NumPy 的开源数据分析工具&#xff0c;用于处理和分析大量数据。Pandas 模块提供了一组高效的工具&#xff0c;可以轻松地读取、处理和…...

js常见的9种报错记录一下

js常见报错语法错误(SyntaxError)类型错误(TypeError)引用错误(ReferenceError)范围错误(RangeError)运行时错误(RuntimeError)网络错误&#xff08;NetworkError&#xff09;内部错误&#xff08;InternalError&#xff09;URI错误&#xff08;URIError&#xff09;eval错误&a…...

ORACLE not available报错处理办法

用sqlplus的时候 连接用户总是出现ORACLE not available 解决办法&#xff1a; 第一步: 请输入用户名: sys as sysdba 输入口令: 已连接到空闲例程。 第二步&#xff1a; 先连接到管理员用户下将用例开启 SQL> startup; ORACLE 例程已经启动。 然后就会出现一下 Total S…...

【Pandas】Python中None、null和NaN

经常混淆。 空值一般表示数据未知、不适用或将在以后添加数据。缺失值指数据集中某个或某些属性的值是不完整的。 一般空值使用None表示&#xff0c;缺失值使用NaN表示。 注意&#xff1a; python中没有null&#xff0c;但是有和其意义相近的None。 目录 1、None 2. NaN …...

线性表的学习

线性表定义 n个类型相同数据元素的有限序列&#xff0c;记作&#xff1a;a0&#xff0c;a1,a2,a3,...ai-1,ai,ai1...an-1(这里的0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;i-1,i,i1,n-1都是元素的序号) 特点 除第一个元素无直接前驱。最后一个元素无直接后续&am…...

wordpress 去掉文章作者/新手做销售怎么开发客户

文章目录合并形状梳理合并形状梳理 我们在使用PPT插入形状的时候&#xff0c;默认是有很多形状的&#xff0c;但是这些形状都是有固定格式的&#xff0c;假如我们需要绘制图标&#xff0c;那么应该怎么做呢&#xff0c;因此&#xff0c;我们就需要学习合并形状来达到我们的需求…...

山东平台网站建设公司/推广费用一般多少

第一种途径&#xff1a;ginput()函数ginput提供了一个十字光标使我们能更精确的选择我们所需要的位置&#xff0c;并返回坐标值。函数调用形式为&#xff1a;[x,y] ginput(n)[x,y] ginput[x,y,button] ginput(...)对于[x,y] ginput(n)&#xff0c;能使你从当前的坐标系中读…...

深圳哪里做网站/优化教程网下载

Python中strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。注意&#xff1a;该方法只能删除开头或是结尾的字符&#xff0c;不能删除中间部分的字符。它的函数原型&#xff1a;string.strip(s[, chars])&#xff0c;它返回的是字符串的副本&#xff0…...

杭州企业网站开发/站长工具综合查询系统

有时候我们自己内心经常在斗争&#xff0c;经常有两种声音在争论&#xff0c;比如 是看书呢&#xff1f;还是看电影呢&#xff1f;其实这是一个很大的话题&#xff0c;感性与理性的较量&#xff0c;而今天所说的就是认识到产生这种现象的原因。我们在一定程度上被原始社会进化的…...

做的网站/免费做网站的网站

哪位高手帮忙看看下面MATLAB进行模拟股票价格的程序是否有问题啊function [St]Stock(T,dt,S0,mu,sigma)% Simulation of a Stock Price with constant Volatilityclc;Tinput(Specify the desired long time period );dtinput(Specify time internals );S0input(Specify the ini…...

搭建一个简单的网站/网站seo优化教程

大家好&#xff0c;今天给大家详解一下Android中Activity的生命周期&#xff0c;我在前面也曾经讲过这方面的内容&#xff0c;但是像网上大多数文章一样&#xff0c;基本都是翻译Android API&#xff0c;过于笼统&#xff0c;相信大家看了&#xff0c;会有一点点的帮助&#xf…...