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
方法后,子线程会和父线程分离,子线程父线程同时进行,主线程可以不用等待其他线程结束就可以正常结束。
ℹ️需要注意的问题:
- 当子线程中有主线程对象的引用时,如果主线程先于子线程结束,这些引用的对象就会被释放,导致子线程中的引用出错
- 主线程中用来构造子线程的对象(对象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
简化了mutex
的lock
和unlock
管理 -
unique_lock
比lock_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::launch::deferred
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_mutex
和recursive_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并发与多线程 子线程结束,主线程不能结束,否则会出错,和java不一样。 可以用join的方式让主线程等待子线程执行结束。 quickStart 线程相关头文件 #include <thread> 使用全局函数构造一个线程对象 #include <iostream> #…...
Vinylsulfone PEG Biotin,Biotin-PEG-VS,生物素聚乙二醇乙烯砜,VS基团容易与游离巯基发生反应
●中文名:乙烯砜PEG生物素,生物素聚乙二醇乙烯砜 ●英文名:Vinylsulfone PEG Biotin, VS-PEG-Biotin,Vinyl sulfone-PEG-Biotin,Biotins-PEG-sulfone Vinyl●产品理化指标: CAS号:N/A 分子量&am…...
论文学习——Tune-A-Video
Tune-A-Video: One-Shot Tuning of Image Diffusion Models for Text-to-Video Generation Abstract 本文提出了一种方法,站在巨人的肩膀上——在大规模图像数据集上pretrain并表现良好的 text to image 生成模型——加入新结构并进行微调,训练出一套 …...
C++类与对象part1
目录 1.类的6个默认函数 2.构造函数(相当于init) 3.析构函数 (相当于destroy) 4.拷贝构造函数 赋值运算符重载 运算符重载 赋值运算符重载 引入: 你知道为什么cout可以自动识别类型吗? 其实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是一家专门提供软件测试解决方案的公司,Parasoft通过其经过市场验证的自动化软件测试工具集成套件,帮助企业持续交付高质量的软件。Parasoft的技术支持嵌入式、企业和物联网市场,通过将静态代码分析和单元测试、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的用户,并指定/home/hello为根目录useradd -d /home/hello -m hello 设置密码 ,密码会输入两次,一次设置密码,一次确认密码,两次密码要输入的一样passwd hellouseradd的常用参数含义-d指定用户登入时的主目录&am…...
RDD(弹性分布式数据集)总结
文章目录一、设计背景二、RDD概念三、RDD特性四、RDD之间的依赖关系五、阶段的划分六、RDD运行过程七、RDD的实现一、设计背景 1.某些应用场景中,不同计算阶段之间会重用中间结果,即一个阶段的输出结果会作为下一个阶段的输入。如:迭代式算法…...
服务器版RstudioServer安装与配置详细教程
Docker部署Rstudio server 背景:如果您想在服务器上运行RstudioServer,可以按照如下方法进行操作,笔者测试时使用腾讯云服务器(系统centos7),需要在管理员权限下运行 Rstudio 官方提供了使用不同 R 版本的 …...
如何在Java中将一个列表拆分为多个较小的列表
在Java中,有多种方法可以将一个列表拆分为多个较小的列表。在本文中,我们将介绍三种不同的方法来实现这一目标。 方法一:使用List.subList()方法 List接口提供了一个subList()方法,它可以用来获取列表中的一部分元素。我们可以使…...
TryHackMe-Inferno(boot2root)
Inferno 现实生活中的机器CTF。该机器被设计为现实生活(也许不是?),非常适合刚开始渗透测试的新手 “在我们人生旅程的中途,我发现自己身处一片黑暗的森林中,因为直截了当的道路已经迷失了。我啊…...
微信原生开发中 JSON配置文件的作用 小程序中有几种JSON配制文件
关于json json是一种数据格式,在实际开发中,JSON总是以配制文件的形式出现,小程序与不例外,可对项目进行不同级别的配制。Q:小程序中有几种配制文件A:小程序中有四种配制文件分别是:project.config.json si…...
【python】为什么使用python Django开发网站这么火?
关注“测试开发自动化” 弓中皓,获取更多学习内容) Django 是一个基于 Python 的 Web 开发框架,它提供了许多工具和功能,使开发者可以更快地构建 Web 应用程序。以下是 Django 开发中的一些重要知识点: MTV 模式&#…...
Java设计模式(五)—— 责任链模式
责任链模式定义如下:使多个对象都有机会处理请求,从而避免请求的发送者与接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。 适合使用责任链模式的情景如下: 有许多对…...
VMLogin:虚拟浏览器提供的那些亮眼的功能
像VMLogin这样的虚拟浏览器具有多种功能,如安全的浏览环境、可定制的设置、跨平台的兼容性、更快的浏览速度、广告拦截等等。 虚拟浏览器的不同功能可以为您做什么? 使用虚拟浏览器是浏览互联网和完成其他任务的安全方式,没有风险。您可以在…...
第一个错误的版本
题目 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 假设你有 n 个版本 [1, 2, …, n],你想找出…...
2023爱分析·AIGC市场厂商评估报告:拓尔思
AIGC市场定义 市场定义: AIGC,指利用自然语言处理技术(NLP)、深度神经网络技术(DNN)等人工智能技术,基于与人类交互所确定的主题,由AI算法模型完全自主、自动生成内容,…...
MobTech|场景唤醒的实现
什么是场景唤醒? 场景唤醒是moblink的一项核心功能,可以实现从打开的Web页面,一键唤醒App,并恢复对应的场景。 场景是指用户在App内的某个特定页面或状态,比如商品详情页、活动页、个人主页等。每个场景都有一个唯一…...
不在路由器上做端口映射,如何访问局域网内网站
假设现在外网有一台ADSL直接拨号上网的电脑,所获得的是公网IP。然后它想访问局域网内的电脑上面的网站,那么就需要在路由器上做端口映射。在路由器上做端口映射的具体规则是:将所有发向自己端口的数据,都转发到内网的计算机。 访…...
ChatGPT 辅助科研写作
前言 总结一些在科研写作中 ChatGPT 的功能,以助力提升科研写作的效率。 文章目录前言一、ChatGPT 简介1. ChatGPT 普通版与 Plus 版的区别1)普通账号2)Plus账号二、New Bing 简介1. 快速通过申请三、辅助学术写作1. 改写论文表述2. 语言润色…...
MySQL最大建议行数 2000w,靠谱吗?
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎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…...
重装系统如何做到三步装机
小白三步版在给电脑重装系统的过程中,它会提供系统备份、还原和重装等多种功能。下面也将介绍小白三步版的主要功能,以及使用技巧和注意事项。 主要功能 系统备份和还原:小白三步版可以帮助用户备份系统和数据,以防止重要数据丢失…...
蓝桥杯单片机第十一届省赛客观题(深夜学习——单片机)
第一场 (1)模电——》多级放大电路 阻容耦合,只通交流,不通直流。 变压器耦合,只通交流,不通直流。 光电耦合,主要是起隔离作用,更多的用在非线性的应用电路中 (2&a…...
Pandas对Excel文件进行读取、增删、打开、保存等操作的代码实现
文章目录前言一、Pandas 的主要函数包括二、使用步骤1.简单示例2.保存Excel操作3.删除和添加数据4.添加新的表单总结前言 Pandas 是一种基于 NumPy 的开源数据分析工具,用于处理和分析大量数据。Pandas 模块提供了一组高效的工具,可以轻松地读取、处理和…...
js常见的9种报错记录一下
js常见报错语法错误(SyntaxError)类型错误(TypeError)引用错误(ReferenceError)范围错误(RangeError)运行时错误(RuntimeError)网络错误(NetworkError)内部错误(InternalError)URI错误(URIError)eval错误&a…...
ORACLE not available报错处理办法
用sqlplus的时候 连接用户总是出现ORACLE not available 解决办法: 第一步: 请输入用户名: sys as sysdba 输入口令: 已连接到空闲例程。 第二步: 先连接到管理员用户下将用例开启 SQL> startup; ORACLE 例程已经启动。 然后就会出现一下 Total S…...
【Pandas】Python中None、null和NaN
经常混淆。 空值一般表示数据未知、不适用或将在以后添加数据。缺失值指数据集中某个或某些属性的值是不完整的。 一般空值使用None表示,缺失值使用NaN表示。 注意: python中没有null,但是有和其意义相近的None。 目录 1、None 2. NaN …...
线性表的学习
线性表定义 n个类型相同数据元素的有限序列,记作:a0,a1,a2,a3,...ai-1,ai,ai1...an-1(这里的0,1,2,3,i-1,i,i1,n-1都是元素的序号) 特点 除第一个元素无直接前驱。最后一个元素无直接后续&am…...
wordpress 去掉文章作者/新手做销售怎么开发客户
文章目录合并形状梳理合并形状梳理 我们在使用PPT插入形状的时候,默认是有很多形状的,但是这些形状都是有固定格式的,假如我们需要绘制图标,那么应该怎么做呢,因此,我们就需要学习合并形状来达到我们的需求…...
山东平台网站建设公司/推广费用一般多少
第一种途径:ginput()函数ginput提供了一个十字光标使我们能更精确的选择我们所需要的位置,并返回坐标值。函数调用形式为:[x,y] ginput(n)[x,y] ginput[x,y,button] ginput(...)对于[x,y] ginput(n),能使你从当前的坐标系中读…...
深圳哪里做网站/优化教程网下载
Python中strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。它的函数原型:string.strip(s[, chars]),它返回的是字符串的副本࿰…...
杭州企业网站开发/站长工具综合查询系统
有时候我们自己内心经常在斗争,经常有两种声音在争论,比如 是看书呢?还是看电影呢?其实这是一个很大的话题,感性与理性的较量,而今天所说的就是认识到产生这种现象的原因。我们在一定程度上被原始社会进化的…...
做的网站/免费做网站的网站
哪位高手帮忙看看下面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优化教程
大家好,今天给大家详解一下Android中Activity的生命周期,我在前面也曾经讲过这方面的内容,但是像网上大多数文章一样,基本都是翻译Android API,过于笼统,相信大家看了,会有一点点的帮助…...