【C++11 —— 线程库】

C++11 —— 线程库

  • thread类介绍
  • 线程函数参数
  • 原子性操作库(atomic)
  • lock_guard与unique_lock
    • mutex的种类
    • lock_guard
    • unique_lock
  • 两个线程交替打印奇偶数

thread类介绍

C++11之前,涉及到多线程的问题,都是和平台相关的,比如windows和Linux下各有自己的接口,这使得代码的可移植性比较差。 C++11的线程库提供了对多线程编程的支持,使得程序能够并发执行多个任务。从而不再需要依赖于第三方库,而且在原子操作中引入了原子类的概念。在使用标准库里的线程,必须包含<thread>头文件。主要的组件包括:

  • 线程 (std::thread)
  • 互斥量 (std::mutex)
  • 条件变量 (std::condition_variable)
  • 原子操作 (std::atomic)
  • 未来和承诺 (std::future 和 std::promise)

基本函数:

函数名功能
thread()构造一个线程对象,没有关联任何线程函数,即没有启动任何进程。
thread(fn,args1,args2,...) 构造一个线程对象,并关联线程函数fn,args1,args2,…为线程函数的参数
get_id()获取进程id
joinable()判读线程是否还在执行,joinable代表的是一个正在执行中的线程。
join()
detach()在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

注意:

  1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
  2. 当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程
#include <thread>int main()
{thread t1;cout << t1.get_id() << endl;return 0;
}

在这里插入图片描述
get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含了一个结构体:

// vs下查看
typedef struct
{ /* thread identifier for Win32 */
void *_Hnd; /* Win32 HANDLE */
unsigned int _Id;
} _Thrd_imp_t;
  1. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
    线程函数一般情况下可按照以下三种方式提供:
    • 函数指针
    • lambda表达式
    • 函数对象
#include <iostream> // 引入输入输出流库
#include <thread>   // 引入线程库using namespace std; // 使用标准命名空间// 线程函数,接受一个整数参数并打印
void ThreadFunc(int a) {cout << "Thread1 -> " << a << endl; // 打印线程1的输出
}// 定义一个类TF,重载了()运算符
class TF {
public:// 当对象被调用时执行的函数void operator()() {cout << "Thread3" << endl; // 打印线程3的输出}
};int main() {// 创建一个线程,传入函数指针和参数10thread t1(ThreadFunc, 10);// 创建一个线程,传入lambda表达式thread t2([]() {cout << "Thread2" << endl; // 打印线程2的输出});// 创建一个TF类的对象TF tf;// 创建一个线程,传入函数对象tfthread t3(tf);	//传入tf对象,该对象的operator()会被自动调用// 等待线程t1完成t1.join();// 等待线程t2完成t2.join();// 等待线程t3完成t3.join();// 打印主线程的输出cout << "main thread ..." << endl;return 0; // 返回0,表示程序正常结束
}

在这里插入图片描述
4. thread类是防拷贝的,不允许拷贝构造以及赋值但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。这种设计确保了每个线程对象只能与一个线程执行关联,避免了潜在的资源竞争和不确定性。

void ThreadFunc(int a)
{cout << "Thread -> " << a << endl;
}int main()
{//创建一个线程对象t1,执行ThreadFunc并传入10thread t1(ThreadFunc, 10);//移动t1到t2thread t2 = move(t1);//检查t1是否可以继续使用if (!t1.joinable()){cout << "t1在move之后不再运行" << endl;}//等待t2完成t2.join();cout << "main thread..." << endl;return 0;
}

在这里插入图片描述

  1. 可以通过 joinable() 函数判断线程是否是有效的,如果是以下任意情况,则线程无效
    • 采用无参构造函数构造的线程对象
    • 线程对象的状态已经转移给其他线程对象
    • 线程已经调用join或者detach结束

并发与并行的区别?


并发 (Concurrency)
并发指的是在同一时间段内处理多个任务。它强调的是任务之间的交替执行,通常在单核处理器上实现。虽然在某一时刻只有一个任务在执行,但系统通过快速切换任务,使得多个任务看起来像是同时进行。例如,当一个程序在等待用户输入时,它可以在后台处理其他任务,这种情况下我们称之为并发。
例子:
想象一个人在吃饭,突然接到电话。他先放下筷子去接电话,然后再继续吃饭。在这个过程中,虽然他不能同时吃饭和接电话,但他能够在同一时间段内处理这两个任务,这就是并发。


并行 (Parallelism)
并行则是指在同一时刻同时执行多个任务。它通常需要多核或多处理器系统来实现,允许多个任务真正同时运行而不互相干扰。例如,在一个四核CPU上,可以同时运行四个不同的程序,每个程序都在独立的核心上执行。
例子
继续以上的例子,如果有两个人坐在同一张桌子上,一个人吃饭,另一个人打电话,他们可以同时进行各自的活动,这就是并行。

线程函数参数

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的, 因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。


void ThreadFunc1(int& x)
{x += 10;
}void ThreadFunc2(int* x)
{*x += 10;
}int main()
{int a = 10;//在线程t1中修改a的值失败,因为虽然线程函数的参数是引用方式,但是实际上传入的是线程栈中的拷贝,并非a本身。/*thread t1(ThreadFunc1, a);		会编译失败t1.join();cout << "a: " << a << endl;cout << "--------------" << endl;*///可以通过传入ref函数来修改a的值thread t2(ThreadFunc1, ref(a));t2.join();cout << "a: " << a << endl;cout << "--------------" << endl;//地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << "a: " << a << endl;return 0;
}

在这里插入图片描述

当类成员函数作为线程参数时:

class MyClass
{
public:void memberFunction(int value){cout << "Value: " << value << ", from Thread: " << this_thread::get_id() << endl;}void startThread(int value){thread t (&MyClass::memberFunction, this, value);t.join();}
};int main()
{MyClass  obj;obj.startThread(42);return 0;
}

在这里插入图片描述
注意:如果是类成员函数作为线程参数时,必须将this作为线程函数参数。

原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。 比如:

#include <iostream>
using namespace std;
#include <thread>
unsigned long sum = 0L;void fun(size_t num)
{for (size_t i = 0; i < num; ++i)sum++;
}
int main()
{cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 100000);thread t2(fun, 100000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}

上面这段代码使用了两个线程来并发地增加全局变量 sum 的值,因为没有保护机制,并且+的动作并非是元子的,所以会导致结果出异常的情况。
在这里插入图片描述
C++98中传统的解决方式:可以对共享修改的数据可以加锁保护。


void fun(size_t num)
{for (size_t i = 0; i < num; ++i){m.lock();m.unlock();}	sum++;}

在这里插入图片描述

虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。
因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。

在这里插入图片描述
注意:需要使用以上原子操作变量时,必须添加头文件

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;atomic_long sum = 0;void fun(size_t num)
{for (size_t i = 0; i < num; ++i)sum++;}
int main()
{cout << "Before joining,sum = " << sum << std::endl;thread t1(fun, 100000);thread t2(fun, 100000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}

在这里插入图片描述

C++11中,程序员不需要对原子类型变量进行加锁解锁操作线程能够对原子类型变量互斥的访问。
更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型。

atmoic<T> t; // 声明一个类型为T的原子类型变量t

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

#include <atomic>
int main()
{
atomic<int> a1(0);
//atomic<int> a2(a1); // 编译失败
atomic<int> a2(0);
//a2 = a1; // 编译失败
return 0;
}

lock_guard与unique_lock

在多线程环境下,如果想要保证某个变量的安全性,只要将其设置成对应的原子类型即可,即高效又不容易出现死锁问题。但是有些情况下,我们可能需要保证一段代码的安全性,那么就只能通过锁的方式来进行控制。

比如:一个线程对变量number进行加一100次,另外一个减一100次,每次操作加一或者减一之后,输出number的结果,要求:number最后的值为1。

可以声明一个全局变量number,分别通过两个线程来对这个变量进行++--运算,并且每次操作后打印当前number值。


#include <thread>
#include <mutex>
int number = 0;
mutex g_lock;
int ThreadProc1()
{for (int i = 0; i < 100; i++){g_lock.lock();++number;cout << "thread 1 :" << number << endl;g_lock.unlock();}return 0;
}
int ThreadProc2()
{for (int i = 0; i < 100; i++){g_lock.lock();--number;cout << "thread 2 :" << number << endl;g_lock.unlock();}return 0;
}
int main()
{thread t1(ThreadProc1);thread t2(ThreadProc2);t1.join();t2.join();cout << "number:" << number << endl;return 0;
}

在这里插入图片描述
上述代码的缺陷:锁控制不好时,可能会造成死锁,最常见的比如在锁中间代码返回,或者在锁的范围内抛异常。因此:C++11采用RAII的方式对锁进行了封装,即lock_guardunique_lock


#include <iostream>
#include <thread>
#include <mutex>using namespace std;int number = 0;
mutex g_lock;int ThreadProc1()
{for (int i = 0; i < 100; i++){lock_guard<mutex> lock(g_lock); number++;cout << "thread 1: " << number << endl;}return 0;
}int ThreadProc2()
{for (int i = 0; i < 100; i++){lock_guard<mutex> lock(g_lock);number--;cout << "thread 2: " << number << endl;}return 0;
}int main() {thread t1(ThreadProc1);thread t2(ThreadProc2);t1.join();t2.join();cout << "number:" << number << endl;return 0;
}

mutex的种类

C++11中,Mutex总共包了四个互斥量的种类:

  1. std::mutex

C++11提供的最基本的互斥量,该类的对象之间不能拷贝,也不能进行移动。 mutex最常用的三个函数:

函数名函数功能
lock()上锁:锁住互斥量
unlock()解锁:释放对互斥量的所有权
try_lock()尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞

注意:线程函数调用lock()时,可能会发生以下三种情况:

  • 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁
  • 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)

线程函数调用try_lock()时,可能会发生以下三种情况:

  • 如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock释放互斥量
  • 如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉
  • 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)
  1. std::recursive_mutex

其允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数 unlock(),除此之外,std::recursive_mutex 的特性和std::mutex大致相同。

  1. std::timed_mutex
    std::mutex 多了两个成员函数,try_lock_for()try_lock_until()
  • try_lock_for()
    接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与std::mutextry_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false
  • try_lock_until()
    接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false
  1. std::recursive_timed_mutex
    它结合了递归锁和定时锁的特性。当多个线程可能同时请求对同一资源的访问,但又希望能够控制等待时间时,使用定时功能可以避免长时间阻塞。

lock_guard

std::lock_guradC++11 中定义的模板类。定义如下:


template<class _Mutex>
class lock_guard
{
public:// 在构造lock_gard时,_Mtx还没有被上锁explicit lock_guard(_Mutex& _Mtx): _MyMutex(_Mtx){_MyMutex.lock();}// 在构造lock_gard时,_Mtx已经被上锁,此处不需要再上锁lock_guard(_Mutex& _Mtx, adopt_lock_t): _MyMutex(_Mtx){}~lock_guard() _NOEXCEPT{_MyMutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;
private:_Mutex& _MyMutex;
};

通过上述代码可以看到,lock_guard类模板主要是通过RAII的方式,对其管理的互斥量进行了封装,在需要加锁的地方,只需要用上述介绍的任意互斥体实例化一个lock_guard,调用构造函数成功上锁,出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁,可以有效避免死锁问题。

lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock

unique_lock

lock_gard类似,unique_lock类模板也是采用RAII的方式对锁进行了封装,并且也是以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。在构造(或移动(move)赋值)时,unique_lock 对象需要传递一个 Mutex 对象作为它的参数,新创建的unique_lock 对象负责传入的Mutex对象的上锁和解锁操作。使用以上类型互斥量实例化unique_lock的对象时,自动调用构造函数上锁,unique_lock对象销毁时自动调用析构函数解锁,可以很方便的防止死锁问题。

lock_guard不同的是,unique_lock更加的灵活,提供了更多的成员函数:

  • 上锁/解锁操作locktry_locktry_lock_fortry_lock_until unlock
  • 修改操作:移动赋值、交换(swap:与另一个unique_lock对象互换所管理的互斥量所有权)、释放(release:返回它所管理的互斥量对象的指针,并释放所有权)
  • 获取属性owns_lock (返回当前对象是否上了锁)、operator bool()owns_lock() 的功能相
    同)、mutex(返回当前unique_lock所管理的互斥量的指针)。

两个线程交替打印奇偶数

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>void Print()
{mutex mtx;condition_variable c;int n = 100;bool flag = true;thread t1([&](){int i = 0;while (i < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return flag; });cout << "thread 1 : " << i << endl;flag = false;i += 2;c.notify_one();}});thread t2([&](){int j = 1;while (j < n){unique_lock<mutex> lock(mtx);c.wait(lock, [&]()->bool {return !flag; });cout << "thread 2 : " << j << endl;flag = true;j += 2;c.notify_one();}});t1.join();t2.join();
}int main()
{Print();return 0;
}

这段代码使用了条件变量std::condition_variable)和互斥量std::mutex)来实现两个线程交替打印奇偶数。

  1. Print() 函数内部,创建了一个互斥量 mtx 和一个条件变量 c。用于实现线程同步。
  2. 首先将flag设为true,控制线程1首先打印,打印1.
  3. 当线程1打印完之后,设置flagfalse,同时使用notify_one()唤醒线程2。

wait函数原型
C++11std::condition_variable中,wait函数的原型如下:

template< class Predicate >
void wait( std::unique_lock<mutex>& lock, Predicate pred );

其中:

  • lock是一个对std::mutexstd::unique_lock引用,用于管理互斥量的锁。
  • pred是一个返回bool类型的可调用对象(如函数对象或lambda表达式),用于指定等待条件。

在上面的代码中:

c.wait(lock, [&]()->bool {return flag; });
  • lock是一个对std::mutexstd::unique_lock的引用,用于管理互斥量的锁。
  • [&]()->bool {return flag; }是一个lambda表达式,捕获外部变量flag的引用并返回它的值。这个lambda表达式就是pred参数。

当调用c.wait(lock, [&]()->bool {return flag; });时,它会执行以下步骤:

  1. 检查lambda表达式[&]()->bool {return flag; }的返回值。如果返回true,则wait函数立即返回,不会阻塞
  2. 如果lambda表达式返回falsewait函数会释放lock 并阻塞当前线程。
  3. 当另一个线程调用notify_one()notify_all()时,被阻塞的线程会被唤醒。
  4. 被唤醒的线程会 重新获取lock并再次检查lambda表达式的返回值。如果返回true,则wait函数返回;否则继续阻塞。

这个过程会一直重复,直到lambda表达式返回true或者线程被取消。使用lambda表达式作为pred参数可以方便地指定等待条件。

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

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

相关文章

Android中的冷启动,热启动和温启动

在App启动方式中分为三种&#xff1a;冷启动&#xff08;cold start&#xff09;、热启动&#xff08;hot start&#xff09;、温启动&#xff08;warm start&#xff09; 冷启动&#xff1a; 系统不存在App进程&#xff08;App首次启动或者App被完全杀死&#xff09;时启动A…

Bluetooth Core6.0中关于Channel Sounding设置初始化过程详细介绍

目录 第一步&#xff1a;读取本地设备CS支持功能&#xff1a; Num_Config_Supported ​Max_Consecutive_Procedures_Supported ​Num_Antennas_Supported ​Max_Antenna_Paths_Supported ​Roles_Supported ​Modes_Supported ​RTT_Capability&#xff0c;RTT_AA_Only_…

【第12章】SpringBoot之SpringBootActuator服务监控(上)

文章目录 前言一、准备1. 地址和端口配置2. 引入依赖3. Actuator Properties 二、使用1. Beans (beans)2. Configuration Properties (configprops)3. Environment (env)4. Health (health)5. Heap Dump (heapdump)6. Mappings (mappings)7. Metrics (metrics)8. Thread Dump (…

浅谈vue2.0与vue3.0的区别(整理十六点)

目录 1. 实现数据响应式的原理不同 2. 生命周期不同 3. vue 2.0 采用了 option 选项式 API&#xff0c;vue 3.0 采用了 composition 组合式 API 4. 新特性编译宏 5. 父子组件间双向数据绑定 v-model 不同 6. v-for 和 v-if 优先级不同 7. 使用的 diff 算法不同 8. 兄弟组…

小米,B站网络安全岗位笔试题目+答案

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 《Java代码审…

面试官问:请描述一次你成功解决问题的经历?

面试官为什么要这么问&#xff1f; 面试官问你描述一次成功解决问题的经历&#xff0c;主要是为了评估你的几个关键方面&#xff1a; 问题解决能力&#xff1a;了解你在面对挑战时的思维方式和应对策略。 决策能力&#xff1a;考察你在压力下做出明智决定的能力。 沟通技巧&am…

集团人事管理信息化目标及重点工作内容【数字化规划】

人力资源管理能力模型通常被细分为六个主要支柱&#xff0c;这些支柱共同构成了人力资源管理的核心框架。每个支柱分别涵盖了不同的HR职责和技能&#xff0c;以下是这六支柱能力模型的详细介绍&#xff1a; 1. 人力资源规划与策略&#xff08;HR Planning and Strategy&#xf…

自修C++PrimerPlus--类型转换、右值引用、引用中的类对象

目录 1.类型转换介绍 2.关闭vs2022的报警系统 3.string里面的I/O 4.引用和左值引用 4.1左值和右值的说明 4.2具体的代码演示 4.3字符和字符串的const区分 4.4右值引用的示例介绍 5.将引用应用于类对象 6.函数和C风格字符串 6.1两者的区别 6.2演示案例 1.类型转换介…

学习图解算法 使用C语言

图解算法 使用C语言 也就是通过C语言实现各种算法 链接&#xff1a;百度云盘 提取码&#xff1a;1001

中国空间计算产业链发展分析

2024中国空间计算产业链拆解 空间计算设备主要包括AR、VR、MR等终端设备。VR设备通常包括头戴式显示器&#xff08;VR头盔&#xff09;、手柄或追踪器等组件&#xff0c;用以完全封闭用户视野&#xff0c;营造虚拟环境体验。这些设备配备高分辨率显示屏、内置传感器和跟踪器。 …

哪些行业需要办理网络文化经营许可证?

网络文化经营许可证&#xff0c;是指经文化行政部门和电信管理机构批准&#xff0c;颁发给从事经营性互联网文化活动的互联网信息服务提供者的市场合法准入资质。经营性互联网文化活动是指以营利为目的&#xff0c;通过向上网用户收费或者电子商务、广告、赞助等方式获取利益&a…

linux 内核代码学习(九)--Linux内核启动和文件系统

一个比较顺手的学习平台可以达到事半功倍的效果&#xff0c;这里使用的平台环境主要是利用了主机和从机间的文件共享&#xff0c;以及从机自带的编译环境可以比较顺利的编译busybox1.0版本&#xff0c;方便进行内核和文件系统的测试了学习。 主机环境&#xff1a;vmware7.0win1…

Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 内存分配和回收规则

文章目录 垃圾回收机制堆空间的基本结构内存分配和回收规则对象优先在 Eden 区分配分配担保机制 大对象直接进入老年代长期存活的对象进入老年代主要进行 GC 的区域部分收集 (Partial GC)&#xff1a;Minor GCMajor/Old GCMixed GC 整堆收集&#xff08;Full GC&#xff09; 空…

时序必读论文10|ICLR23 Crossformer 跨维度依赖的多变量时序预测模型

论文标题&#xff1a;iCROSSFORMER : TRANSFORMER UTILIZING CROSS DIMENSION DEPENDENCY FOR MULTIVARIATE TIME SERIES FORECASTING 开源代码&#xff1a;https://github.com/Thinklab-SJTU/Crossformer 前言 Crossformer是一篇非常典型的在transformer基础上魔改注意力机…

24/9/16 算法笔记 评估模型

评估机器学习模型的性能是一个关键步骤&#xff0c;它可以帮助我们了解模型在实际应用中的表现。以下是一些常用的评估模型的方法&#xff1a; 准确率&#xff08;Accuracy&#xff09;&#xff1a; 最常见的评估指标&#xff0c;表示正确预测的样本数占总样本数的比例。 精确度…

Linux命令:文本处理工具sed详解

目录 一、概述 二、用法 1、基本语法 2、常用选项 3、命令格式 4、编辑命令 5、获取帮助 三、 示例 1、替换字符串 2、删除行 &#xff08;1&#xff09;删除包含"string"的所有行 ​编辑 &#xff08;2&#xff09;删除从第1行到第10行的所有行 3、插…

MySQL篇(运算符)(持续更新迭代)

目录 一、简介 二、运算符使用 1. 算术运算符 1.1. 加法运算符 1.2. 减法运算符 1.3. 乘法与除法运算符 1.4. 求模&#xff08;求余&#xff09;运算符 2. 比较运算符 2.1. 等号运算符 2.2. 安全等于运算符 2.3. 不等于运算符 2.4. 空运算符 2.5. 非空运算符 2.6.…

java -- JDBC

一.JDBC概述: 过java语言操作数据库中的数据。 1.JDBC概念 JDBC&#xff08;Java DataBase Connectivity,java数据库连接&#xff09;是一种用于 执行SQL语句的Java API。JDBC是Java访问数据库的标准规范&#xff0c;可以 为不同的关系型数据库提供统一访问&#xff0c;它由…

CORS跨域请求共享

参考文章: https://xz.aliyun.com/t/12001?time__1311GqGxRGiti%3Dd052x%2BxCwx7qGIxpbDulE%3DoD https://blog.csdn.net/weixin_46622976/article/details/128452494 跨域资源共享 自己的理解&#xff0c;一般来讲&#xff0c;我们使用未授权的接口漏洞&#xff0c;都是因…

Django学习实战篇四(适合略有基础的新手小白学习)(从0开发项目)

前言&#xff1a; 在本章中&#xff0c;我们开始编写面向用户的界面&#xff0c;其中只涉及简单的HTML结构&#xff0c;不会做太多美化&#xff0c;目的就是把后台创建的数据展示到前台。 从技术上来讲&#xff0c;这一节将涉及Django 中function view和 class-based view 的用…