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

【C++】C++11--- 线程库及详解lock_guard与unique_lock

目录

  • 一、thread类的介绍
  • 二、线程函数参数
  • 三、 原子性操作库
  • 四、lock_guard与unique_lock
    • 4.1、mutex的种类
    • 4.2 lock_guard
    • 4.3 unique_lock

一、thread类的介绍

在C++11之前,涉及到多线程问题,都是和平台相关的,比如**windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,**而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。

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

注意:

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

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

  1. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照以下三种方式提供:
    1.函数指针
    2.lambda表达式
    3.函数对象
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{cout << "Thread1" << a << endl;
}
class TF
{
public:void operator()(){cout << "Thread3" << endl;}
};
int main()
{// 线程函数为函数指针thread t1(ThreadFunc, 10);// 线程函数为lambda表达式thread t2([] {cout << "Thread2" << endl; });// 线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout << "Main thread!" << endl;return 0;
}
  1. thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
  2. 可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效。
    1.采用无参构造函数构造的线程对象
    2.线程对象的状态已经转移给其他线程对象
    3.线程已经调用jion或者detach结束

二、线程函数参数

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

void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;// 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝/*thread t1(ThreadFunc1, a);t1.join();cout << a << endl;*/// 如果想要通过形参改变外部实参时,必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a));t2.join();cout << a << endl; 地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}

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

三、 原子性操作库

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

#include <iostream>
#include <thread>
using namespace std;
unsigned 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, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout << "After joining,sum = " << sum << std::endl;return 0;
}

运行结果:
在这里插入图片描述
C++98中传统的解决方式:可以对共享修改的数据可以加锁保护。

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

运行结果:
在这里插入图片描述
虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对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, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout << "After joining, sum = " << sum << std::endl;return 0;
}

运行结果:
在这里插入图片描述
在C++11中,程序员不需要对原子类型变量进行加锁解锁操作,线程能够对原子类型变量互斥的
访问。更为普遍的,程序员可以使用atomic类模板,定义出需要的任意原子类型

atmoic 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。

#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;system("pause");return 0;
}

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

4.1、mutex的种类

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

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

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

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

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

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

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

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

3. std::timed_mutex
比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until() 。

  • try_lock_for()
    接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
  • try_lock_until()
    接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

4. std::recursive_timed_mutex

4.2 lock_guard

std::lock_gurad 是 C++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。

4.3 unique_lock

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

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

相关文章:

【C++】C++11--- 线程库及详解lock_guard与unique_lock

目录 一、thread类的介绍二、线程函数参数三、 原子性操作库四、lock_guard与unique_lock4.1、mutex的种类4.2 lock_guard4.3 unique_lock 一、thread类的介绍 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如**windows和linux下各有自己…...

第二篇|研究数据哪里来——建筑业

数据是研究和产业发展的重要基石&#xff0c;然而无论是学者、企业还是研究机构往往都面临着“找数据难”的局面。本期将分享一些查找建筑相关的数据及资料的渠道。希望可以帮大家解决这一难题&#xff0c;有用求收藏求收藏求收藏~ 1.政府机构 可以查找国家、地方政府的建筑行…...

numpy ascontiguousarra 学习笔记

目录 numpy ascontiguousarra函数 转换命令&#xff1a; ascontiguousarray等价效果&#xff1a; ascontiguousarray学习笔记 ascontiguousarray函数将一个内存不连续存储的数组转换为内存连续存储的数组&#xff0c;使得运行速度更快。 在昇腾开发版上使用时&#xff0c;…...

【算法|双指针系列No.1】leetcode283. 移动零

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…...

PHP8定义字符串的方法-PHP8知识详解

字符串&#xff0c;顾名思义&#xff0c;就是将一堆字符串联在一起。字符串简单的定义方法是使用英文单引号&#xff08; &#xff09;或英文双引号&#xff08;" "&#xff09;包含字符。另外&#xff0c;还可以使用定界符定义字符串。本文还介绍了字符串的连接符。…...

分享21年电赛F题-智能送药小车-做题记录以及经验分享

这里写目录标题 前言一、赛题分析1、车型选择2、巡线1、OpenMv循迹2、灰度循迹 3、装载药品4、识别数字5、LED指示6、双车通信7、转向方案1、开环转向2、位置环速度环闭环串级转向3、MPU6050转向 二、调试经验分享1、循迹2、识别数字3、转向4、双车通信5、逻辑处理6、心态问题 …...

字符串统计-C语言/Java

描述 计算字符串中含有的不同字符的个数。字符在 ASCII 码范围内( 0~127 &#xff0c;包括 0 和 127 )&#xff0c;换行表示结束符&#xff0c;不算在字符里。不在范围内的不作统计。多个相同的字符只计算一次。数据范围&#xff1a; 1≤n≤500 例如&#xff0c;对于字符串 ab…...

Styled-components,另一种css in js的方案

介绍 Styled-components是一种流行的CSS-in-JS库&#xff0c;它为React和React Native应用程序提供了一种优雅的方式来管理组件的样式。它的设计理念是将CSS样式与组件逻辑紧密绑定在一起&#xff0c;从而使样式在组件层级中作用更加清晰和可维护 使用 安装Styled-components…...

nginx部署以及反向代理多域名实现HTTPS访问

nginx部署以及反向代理多域名实现 1.nginx部署 1.1 编写nginx部署文件 docker-compose.yml version: 3 services: nginx:restart: always image: nginx:1.20container_name: nginx-mainports:- 80:80- 443:443volumes: # 基础配置- /opt/nginx_main/nginx-info/nginx.conf:/…...

24届近5年东华大学自动化考研院校分析

今天给大家带来的是东华大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、东华大学 学校简介 东华大学&#xff08;Donghua University&#xff09;&#xff0c;地处上海市&#xff0c;是教育部直属全国重点大学&#xff0c;国家“双一流”、“211工程”建设高校…...

nacos伪集群启动成功,但是服务注册不上的问题

项目场景&#xff1a; nacos 伪集群启动成功&#xff0c;但是服务未注册上的问题&#xff1a; 问题描述 在学习nacos中&#xff0c;我买了一台阿里云服务器&#xff0c;在其上部署了nginx及三个nacos 端口分别是 8848 8868 8888 我按照正常的流程 解压nacos修改application.p…...

tidevice+appium在windows系统实施iOS自动化

之前使用iOS手机做UI自动化都是在Mac电脑上进行的&#xff0c;但是比较麻烦&#xff0c;后来看到由阿里开源的tidevice工具可以实现在windows上启动WDA&#xff0c;就准备试一下&#xff0c;记录一下过程。 tidevice的具体介绍可以参考一下这篇文章&#xff1a;tidevice 开源&…...

面试热题(LRU缓存)

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否则返回 -1 …...

微信小程序开发【从0到1~入门篇】2023.08

一个小程序主体部分由三个文件组成&#xff0c;必须放在项目的根目录&#xff0c;如下&#xff1a; 文件必须作用app.js是小程序逻辑app.json是小程序公告配置app.wxss否小程序公告样式表 3. 小程序项目结构 一个小程序页面由四个文件组成&#xff0c;分别是&#xff1a; 文…...

P1398 [NOI2013] 书法家

题目描述 输入 #1 3 13 1 1 -1 -1 1 -1 1 1 1 -1 1 1 1 1 -1 1 -1 1 -1 1 -1 1 -1 -1 1 -1 1 -1 -1 1 1 -1 1 1 1 -1 1 1 1 输出 #1 24 输入 #2 3 13 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1…...

【构建卷积神经网络】

构建卷积神经网络 卷积网络中的输入和层与传统神经网络有些区别&#xff0c;需重新设计&#xff0c;训练模块基本一致 全连接层&#xff1a;batch784&#xff0c;各个像素点之间都是没有联系的。 卷积层&#xff1a;batch12828&#xff0c;各个像素点之间是有联系的。 impor…...

SSH 认证原理

SSH协议登录服务器&#xff1a; $ ssh userhost 主要有两种登录方式&#xff1a;第一种为密码口令登录&#xff0c;第二种为公钥登录 密码口令登录 通过密码进行登录&#xff0c;主要流程为&#xff1a; 1、客户端连接上服务器之后&#xff0c;服务器把自己的公钥传给客户端…...

基于DETR (DEtection TRansformer)开发构建MSTAR雷达影像目标检测系统

关于DETR相关的实践在之前的文章中很详细地介绍过&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《DETR (DEtection TRansformer)基于自建数据集开发构建目标检测模型超详细教程》 《书接上文——DETR评估可视化》 基于MSTAR雷达影像数据开发构建目标检测系统&am…...

Java分布式微服务1——注册中心(Eureka/Nacos)

文章目录 基础知识注册中心Eureka注册中心与Ribbon负载均衡1、Eureka注册中心2、Eureka的搭建3、Eureka服务注册4、复制服务实例5、拉取服务6、Ribbon负载均衡的流程及Eureka规则调整&#xff1a;7、Ribbon负载均衡饥饿加载 Nacos注册中心1、服务端Nacos安装与启动2、客户端Nac…...

(文章复现)建筑集成光储系统规划运行综合优化方法matlab代码

参考文献&#xff1a; [1]陈柯蒙,肖曦,田培根等.一种建筑集成光储系统规划运行综合优化方法[J].中国电机工程学报,2023,43(13):5001-5012. 1.基本原理 本文建立的双层耦合模型内、外层分别对应求解容量配置与能量调度问题。外层模型设置光伏与储能容量备选集并将容量配置组合…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...