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

详解【多线程与并发】之线程

目录

1、线程

1.1线程Thread

1.2线程特点

1.3线程函数的原型

1.4Linux对于pthread的API的支持

1.4.1创建一个线程

1.4.2线程的退出

1.5资源分离

2、线程的同步/互斥机制

2.1线程互斥锁

2.1.1初始化线程互斥锁

2.2线程互斥锁的PV 操作

2.2.1P 操作(上锁操作)

2.2.2V 操作(解锁操作)

2.2.3线程互斥锁的销毁操作

3、生产者消费者模型

4、线程条件变量

4.1线程条件变量API

4.1.1初始化/销毁条件变量

4.1.2等待一个条件变量

4.1.3唤醒线程/触发条件变量


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


1、线程

前面讲到进程:为了并发执行任务(程序),现代操作系统才引进 进程 的概念

分析:

  • 创建开销问题:创建一个进程开销:大
    • 子进程需要拷贝父进程的整个地址空间
  • 通信开销问题:进程间的通信
    • 需要用第三方(如:内核
    • P1 -> copy -> 内核 -> copy -> P2
    • 进程间通信代价或者开销也是很大的。进程的地址空间是独立,要通信的话需要用第三方的空 间。

于是,就有人提出能不能在 同一个(同一个进程内部)进程地址空间中进行任务的并发:线程/轻量级进程

1.1线程Thread

线程是一个比进程更小的活动单位。它是进程中的执行路径(执行分支),线程也是并发的一种形式。 进程内部可以存在多个线程,它并发执行,但是进程内部的所有的线程共享整个进程的地址空间

main 函数:进程的主线程

1.2线程特点

  • 创建一个线程要比创建进程开销要小很多
    • 因为创建一个线程的话,不需要拷贝进程的地址空间
  • 实现线程间的通信会更加方便
    • 因为进程内部所有线程共享整个进程地址空间
  • 线程也是一个动态概念:
    • 线程(进程)状态图        
      • 就绪态: ready
      • 运行态: running
      • 阻塞态: blocking
  • 有了线程的概念之后
    • 系统的调度单位就从进程变为线程,资源的分配还是以进程为单位

线程是进程内部的一个指令的执行分支,多个线程就是多个指令序列并发执行。这些指令必须在函数 内部,线程的指令部分肯定是封装一个函数的内部的。这个函数,就称之为:线程函数,一个线程在创 建之后,要执行的指令全部封装在该函数内部,这个线程函数执行完毕之后,该线程的任务也就执行完 了。

1.3线程函数的原型

Thread 的实现有多种,比较常见的为 POSIX 线程: pthread

1.4Linux对于pthread的API的支持

1.4.1创建一个线程

pthread_create :创建一个线程(启动一个线程)

#include <iostream>
#include <pthread.h>
#include <unistd.h>static int st_val = 0;/*线程函数:用于新线程创建之后调用的
*/
void *new_thread(void *data)
{for(;st_val < 20;st_val++){sleep(1);}st_val = 0;pthread_exit(&st_val);
}int main(int argc,const char *argv[])
{// 定义线程id的变量pthread_t tid;// 创建线程int ret = pthread_create(&tid,NULL,new_thread,NULL);if(ret == -1){perror("线程创建失败:");return -1;}// 等待子线程把st_val累加到一百while(1){if(st_val >= 20)break;std::cout << "st_val:" << st_val << std::endl;sleep(1);}return 0;
}

1.4.2线程的退出

  • 线程函数的退出(线程函数的返回)
  • 在线程执行的任意时刻调用 pthread_exit
  • 被别人干掉
    • cancel :被别人取消(其他线程调用 pthread_cancel )
      • t1:pthread_cancel(t2)
        • t1 调用取消函数,取消 t2 , t2 不一定会被取消
          • 因为 t2 能不能被其他线程取消,取决于 t2 线程的一个属性: 取消属性
            • 它是否可以被 cancelled
      • #include <iostream>
        #include <pthread.h>
        #include <unistd.h>static int st_val = 0;/*线程函数:用于新线程创建之后调用的
        */
        void *new_thread(void *data)
        {int oldstate;pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstate);for(;st_val < 20;st_val++){sleep(1);}st_val = 0;
        }int main(int argc,const char *argv[])
        {// 定义线程id的变量pthread_t tid;// 创建线程int ret = pthread_create(&tid,NULL,new_thread,NULL);if(ret == -1){perror("线程创建失败:");return -1;}// 等待子线程把st_val累加到一百while(1){if(st_val >= 20)break;std::cout << "st_val:" << st_val << std::endl;if(st_val == 5){pthread_cancel(tid);break;}sleep(1);}return 0;
        }
        

      • 这个属性叫做: 可以被取消属性
        • PTHREAD_CANCEL_ENABLE :表示该线程可以被取消
        • PTHREAD_CANCEL_DISABLE :表示该线程不能被取消

一个线程退出了,并不是所有资源都会释放。一个线程的退出,它资源释放全部被释放,取决于一个属 性

1.5资源分离

  • detach :分离属性:
    • ENABLE : 分离资源属性
      • 该线程结束,它的所有资源都会自动释放。
    • DISABLE : 不分离资源
      • 该线程结束,会有部分资源不会自动释放,需要其他线程调用 pthread_join 这个函数 才能完全释放。
    • 默认是不分离属性。
#include <iostream>
#include <pthread.h>
#include <unistd.h>static int st_val = 0;/*线程函数:用于新线程创建之后调用的
*/
void *new_thread(void *data)
{// 进入线程函数第一时间就设置分离属性// pthread_detach(pthread_self()); // 设置了资源分离一般就不需要join了for(;st_val < 20;st_val++){sleep(1);}st_val = 0;pthread_exit(&st_val);
}int main(int argc,const char *argv[])
{// 定义线程id的变量pthread_t tid;// 创建线程int ret = pthread_create(&tid,NULL,new_thread,NULL);if(ret == -1){perror("线程创建失败:");return -1;}    std::cout << "等待线程结束"<< std::endl;int *tid_ret_val = NULL;pthread_join(tid,(void**)&tid_ret_val);std::cout << "return value :" << *tid_ret_val << std::endl;return 0;
}

2、线程的同步/互斥机制

为了线程之间,能够去有序的访问共享资源,引用 信号量机制

  • 信号量 : System V 和 POSIX 信号量
  • 线程互斥锁

2.1线程互斥锁

线程互斥锁也是 信号量 ,只不过线程互斥锁,存在于进程地址空间,用于线程间同步和互斥操作,线程 互斥锁它的效率相对信号量来说要高。

线程互斥锁:使用 pthread_mutex_t 的类型来描述一个锁

安装线程 POSIX 帮助手册: sudo apt-get install manpages-posix-dev // 安装posix 帮助手册

2.1.1初始化线程互斥锁

2.2线程互斥锁的PV 操作

2.2.1P 操作(上锁操作)

2.2.2V 操作(解锁操作)

2.2.3线程互斥锁的销毁操作

#include <iostream>
#include <pthread.h>
#include <unistd.h>// 模拟内存
static int memory = 100;// 互斥锁
pthread_mutex_t  mutex;// 线程函数模拟病毒
void *thread_trylock(void *data)
{// 等待一秒,防止线程一进来就直接上锁sleep(1);// 记录自己抢占了多内存int self_memory = 0;// 循环条件while(memory > 0){// 上锁if(0 == pthread_mutex_trylock(&mutex)){// 中间的代码就是临界区memory -= 10;self_memory += 10;sleep(1);// 解锁pthread_mutex_unlock(&mutex);std::cout << "嘿嘿!咱“尝试上锁病毒” 抢占了10字节内存!" << std::endl;}elsestd::cout << "哎!咱“尝试上锁病毒”没有抢内存!" << std::endl;sleep(1);}std::cout << "嘿嘿!咱“尝试上锁病毒” 总共抢占了"<< self_memory <<"字节内存!" << std::endl;return nullptr;
}void *thread_lock(void *data)
{// 等待一秒,防止线程一进来就直接上锁sleep(1);// 记录自己抢占了多内存int self_memory = 0;// 循环条件while(memory > 0){// 上锁pthread_mutex_lock(&mutex);// 中间的代码就是临界区memory -= 10;self_memory += 10;sleep(1);// 解锁pthread_mutex_unlock(&mutex);std::cout << "嘿嘿!咱“死锁上锁病毒” 抢占了10字节内存!" << std::endl;sleep(1);}std::cout << "嘿嘿!咱“死锁上锁病毒” 总共抢占了"<< self_memory <<"字节内存!" << std::endl;return nullptr;
}void *thread_timedlock(void *data)
{// 等待一秒,防止线程一进来就直接上锁sleep(1);//等待的时间struct timespec tm;tm.tv_sec = 1;tm.tv_nsec = 0;// 记录自己抢占了多内存int self_memory = 0;// 循环条件while(memory > 0){// 上锁if(0 == pthread_mutex_timedlock(&mutex,&tm)){// 中间的代码就是临界区memory -= 10;self_memory += 10;sleep(1);// 解锁pthread_mutex_unlock(&mutex);std::cout << "嘿嘿!咱“等待上锁病毒” 抢占了10字节内存!" << std::endl;}else{std::cout << "哎!难受  咱“等待上锁病毒” 等了一秒都没有抢占到内存!" << std::endl;}sleep(1);}std::cout << "嘿嘿!咱“等待上锁病毒” 总共抢占了"<< self_memory <<"字节内存!" << std::endl;return nullptr;
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t tid1;pthread_t tid2;pthread_t tid3;// 启动线程pthread_create(&tid1,NULL,thread_lock,NULL);pthread_create(&tid1,NULL,thread_trylock,NULL);pthread_create(&tid1,NULL,thread_timedlock,NULL);// 阻塞等待子线程结束// pthread_join(tid1,NULL);// pthread_join(tid2,NULL);// pthread_join(tid3,NULL);while(1);pthread_mutex_destroy(&mutex);return 0;
}

3、生产者消费者模型

生产者消费者模型:利用厂商和消费者关系,由生产者线程进行生产(产生任务),再由消费者线程消 费(执行任务),没有任务的时候,消费者等待生产者产生任务。

  • 共享资源的互斥访问问题
    • 信号量/线程互斥锁
  • 当缓冲区(生产者没有产出的时候)没有数据的时候,(消费者)应该怎么办?
    • 1. 不停的去测试,看有没有数据。
      • 轮询访问,但是轮询有缺陷:一直在访问,浪费CPU资源。轮询有时间差,占用总线: Is always busy
    • 让出CPU,当有数据的时候,再唤醒我( wake up ),线程条件变量同步

4、线程条件变量

线程条件变量:在多线程程序设计中,可以用 条件变量 为表示一个特定的条件或者是事件

pthread_cond_t :来描述一个条件变量(类型)

至于条件变量,到底是一个什么事件或者说表示一个什么条件?完全由程序猿去解释这个条件变量所代 表的含义

在条件变量上的三种操作:

  • 初始化
  • 等待一个条件变量(等待该条件变量所表示的事件
  • 唤醒一个线程/触发条件变量(唤醒了正在等待该事件的线程

4.1线程条件变量API

4.1.1初始化/销毁条件变量

4.1.2等待一个条件变量

4.1.3唤醒线程/触发条件变量

注意:广播唤醒和单个唤醒的区别

  • 广播唤醒:唤醒所有等待的线程,去执行任务,但是任务可能不够分,那么没分到的线程继续休眠
  • 单个唤醒:随机唤醒一个线程执行任务,其他线程继续休眠。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
using std::vector;// 线程互斥锁
pthread_mutex_t mutex;// 条件变量
pthread_cond_t cond;// 公共资源
FILE *file=NULL;
std::string str;// 消费者
void *NiuMa(void *data)
{while(1){pthread_mutex_lock(&mutex);// 等待条件变量:任务pthread_cond_wait(&cond,&mutex); // 这里卡死不是循环的卡死,休眠,让出了CPUif(str == "退出"){pthread_mutex_unlock(&mutex);     pthread_exit(NULL);}std::cout << fwrite(str.c_str(),str.size(),1,file) << std::endl;pthread_mutex_unlock(&mutex);std::cout << pthread_self() << "线程:成功写入内容:<" << str << "> "<< std::endl;}}// 主线程:生产者
int main()
{// 初始化pthread_mutex_init(&mutex,NULL);pthread_cond_init(&cond,NULL);// 打开文件file = fopen("./1.txt","w+");vector<pthread_t> tids(10);// 创建10个线程for(int i = 0;i < 10;i++){pthread_create(&tids[0],NULL,NiuMa,NULL);}// 通过输入来发布任务while(1){std::cout << "请输入内容:" ;pthread_mutex_lock(&mutex);std::cin >> str;pthread_mutex_unlock(&mutex);if(str == "退出"){pthread_cond_broadcast(&cond);break;}// 唤醒线程pthread_cond_signal(&cond);sleep(1);}for(int i = 0;i < 10;i++){pthread_join(tids[0],NULL);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);fclose(file);return 0;
}

相关文章:

详解【多线程与并发】之线程

目录 1、线程 1.1线程Thread 1.2线程特点 1.3线程函数的原型 1.4Linux对于pthread的API的支持 1.4.1创建一个线程 1.4.2线程的退出 1.5资源分离 2、线程的同步/互斥机制 2.1线程互斥锁 2.1.1初始化线程互斥锁 2.2线程互斥锁的PV 操作 2.2.1P 操作&#xff08;上锁…...

Linux安全与高级应用(四)深入探索MySQL数据库:安装、管理与安全实践

文章目录 标题&#xff1a;全面解析LAMP平台部署及应用第一部分&#xff1a;LAMP平台概述第二部分&#xff1a;准备工作第三部分&#xff1a;安装和配置PHP第四部分&#xff1a;配置Apache第五部分&#xff1a;测试LAMP平台第六部分&#xff1a;部署phpMyAdmin总结 &#x1f44…...

「iOS」自定义Modal转场——抽屉视图的实现

「iOS」自定义Modal转场——抽屉视图的实现 文章目录 「iOS」自定义Modal转场——抽屉视图的实现前言错误尝试自定义Modal转场实现流程自定义动画类UIPresentationController 成果展示参考文章 前言 在仿写网易云的过程之中&#xff0c;看到学长之前仿写时实现的抽屉视图&…...

【数据结构】顺序结构实现:特殊完全二叉树(堆)+堆排序

二叉树 一.二叉树的顺序结构二.堆的概念及结构三.堆的实现1.堆的结构2.堆的初始化、销毁、打印、判空3.堆中的值交换4.堆顶元素5.堆向上调整算法&#xff1a;实现小堆的插入6.堆向下调整算法&#xff1a;实现小堆的删除7.堆的创建1.堆向上调整算法&#xff1a;建堆建堆的时间复…...

【c++学习技术栈】

c学习技术栈 基础c基础组件中间件框架devops性能目标岗位 基础 计算机网络数据结构与算法操作系统linux c 基础组件 池式组件&#xff1a;线程池&#xff0c;内存池&#xff0c;db数据库连接池原子&#xff0c;无锁队列&#xff0c;ringbuffer&#xff0c;定时器。日志&…...

swift 自定义DatePacker

import Foundationenum AppDatePickerStyle {case KDatePickerDate //年月日case KDatePickerTime //年月日时分case kDatePickerMonth // 年月case KDatePickerSecond //秒}class AppDatePicker: UIView {private let jk_rootView UIApplication.shared.keyWindow!pri…...

MySQL事务,锁,MVCC总结

mysql中最重要的就是事务&#xff0c;其四大特性让我们维持了数据的平衡&#xff0c;一致。那么事务究竟是什么&#xff0c;与什么相关&#xff0c;他的使用步骤&#xff0c;以及使用过程中我们会遇到什么问题呢&#xff1f;下面我们一起学习交流! 1.MySQL的存储引擎&#xff…...

24/8/7 算法笔记 支持向量机回归问题天猫双十一

import numpy as np from sklearn.svm import SVR import matplotlib.pyplot as plt X np.linspace(0,2*np.pi,50).reshape(-1,1) y np.sin(X) plt.scatter(X,y) 建模 线性核函数 svr SVR(kernel linear) svr.fit(X,y.ravel())#变成一维y_ svr.predict(X) plt.scatter(…...

win7系统利用定时启动+脚本实现MySQL文件自动备份

前言 最近接到项目&#xff0c;数据量不大但对运行数据的安全性要求极高&#xff0c;为避免因不可抗拒因素导致的数据丢失&#xff0c;选择机械硬盘作为数据存储盘&#xff0c;并使用脚本方式对文件进行备份 一、脚本 下面为自动备份文件的 脚本&#xff0c;可根据自身情况进…...

基于Java多线程处理数据

基于Java多线程处理数据 背景代码实现 背景 在日常工作中&#xff0c;有一个同步企微客户-学员关系接口的定时任务在执行中随着数据量的不断增长&#xff0c;定时任务的执行结束时间也出现了当天执行不完的情况&#xff0c;影响到了正常业务的运行。基于这种情况&#xff0c;在…...

日常知识点之遇到问题结构体按位构造协议时和期望不一致,研究记录一下

遇到一个问题&#xff0c;在做业务的时候&#xff0c;涉及到协议相关&#xff0c;按位进行设计&#xff0c;用结构体来模拟协议时&#xff0c;发现内存存储和实际目的不一致&#xff0c;知道是大小端以及计算机底层存储逻辑相关&#xff0c;所以研究了一下。 1&#xff1a;简单…...

spring mvc 文件下载

在web中下载的方式大多基于servlet&#xff0c;在web.xml中配置下载路径&#xff0c;这里再介绍json(转成base64字符串)和blob的使用方式 servlet WEB-INF/web.xml <!--url映射--> <servlet-mapping><servlet-name>DowloadServlet</servlet-name>&l…...

Qt WebEngine基于WebEngineScript注入js脚本

在之前的文章中&#xff0c;我们介绍了Qt WebEngine注入js的用法&#xff0c;及runJavaScript()的用法&#xff0c;该方法主要是用在页面加载完成后&#xff0c;为了和网页做一些交互时使用。有时候需要监听网页加载完成的一些状态或信息&#xff0c;则需要网页加载前注入js来实…...

案例分享-国外UI设计界面赏析

国外UI设计倾向于简洁的布局和清晰的排版&#xff0c;减少视觉干扰&#xff0c;提升用户体验。通过合理的色彩搭配和图标设计&#xff0c;营造舒适愉悦的使用氛围。 设计师不拘泥于传统框架&#xff0c;勇于尝试新元素和理念&#xff0c;使界面独特有趣。同时&#xff0c;强调以…...

用PyTorch 从零开始构建 BitNet 1.58bit

我们手动实现BitNet的编写&#xff0c;并进行的一系列小实验证实&#xff0c;看看1.58bit 模型是否与全精度的大型语言模型相媲美&#xff01; 什么是量化以及为什么需要它&#xff1f; 量化是用更少的比特数表示浮点数的过程。当两个数字使用不同的比特数进行量化时&#xf…...

信创安全 | 新一代内网安全方案—零信任沙盒

在当今数字化时代&#xff0c;访问安全和数据安全成为企业面临的重要挑战。传统的边界防御已经无法满足日益复杂的内网办公环境&#xff0c;层出不穷的攻击手段已经让市场单一的防御手段黔驴技穷。当企业面临越来越复杂的网络威胁和数据泄密风险时&#xff0c;更需要一种综合的…...

Redis的回收策略(淘汰策略)

volatile-lru &#xff1a;从已设置过期时间的数据集&#xff08; server.db[i].expires &#xff09;中挑选最近最少使用的数据淘汰 volatile-ttl &#xff1a; 从已设置过期时间的数据集&#xff08; server.db[i].expires &#xff09; 中挑选将要过期的数据淘汰 volatile…...

Electron-builder 打包

项目比较简单&#xff0c;仅使用了 Electron 原生js 安装 electron-builder npm install electron-builder --dev配置 package.json 中的打包命令 {"script":{// ..."dev": "electron .","pack": "electron-builder"} }添…...

笔试练习day3

目录 BC149 简写单词题目解析代码 dd爱框框题目解析解析代码方法一暴力解法方法二同向双指针(滑动窗口) 除2!题目解析解法模拟贪心堆 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1…...

企业想要将大模型技术应用到企业管理中需具备什么条件?

#企业 #企业管理 #大模型 企业想要将大模型技术应用到企业管理中&#xff0c;需要考虑以下几个关键条件&#xff1a; 1.明确的需求定位&#xff1a;企业应首先诊断自身的业务场景、数据、算法、基础设施预算以及战略等能力&#xff0c;明确大模型能够为企业带来的具体赋…...

电子设计竞赛:坡道行驶电动小车设计与实现

1. 四川省电子设计竞赛一等奖作品解析&#xff1a;坡道行驶电动小车去年参加四川省电子设计竞赛时&#xff0c;我们团队选择了C题"坡道行驶电动小车"这个看似简单实则暗藏玄机的题目。经过72小时的连续奋战&#xff0c;最终拿下一等奖。今天就把这个项目的完整实现方…...

前端缓存策略:让你的应用飞起来

前端缓存策略&#xff1a;让你的应用飞起来 一、引言 又到了我这个毒舌工匠上线的时间了&#xff01;今天咱们来聊聊前端缓存策略这个话题。别以为缓存只是后端的事情&#xff0c;前端缓存同样重要。一个好的缓存策略能够大大提高应用的性能和用户体验&#xff0c;让你的应用飞…...

学生评教|高校评教|基于SpringBoot+vue高校学生评教系统 (源码+数据库+文档)

高校学生评教系统 目录 基于SpringBootvue高校学生评教系统 一、前言 二、系统设计 三、系统功能设计 1学生功能模块 2管理员功能模块 3老师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&a…...

华泰证券2027届校招启动|提前批+国际管培+金融科技,三个专场一次说清

导读很多同学还在等“春招后半场捡漏”&#xff0c;但现实已经变了。头部企业的优质岗位&#xff0c;正在通过提前批 专项项目提前锁定人选。如果你现在才开始准备&#xff0c;很可能连入场资格都拿不到。这次华泰证券的校招&#xff0c;就是一个非常典型的信号&#xff1a;提…...

如何通过arknights-ui实现明日方舟界面定制?解锁个性化游戏体验新方式

如何通过arknights-ui实现明日方舟界面定制&#xff1f;解锁个性化游戏体验新方式 【免费下载链接】arknights-ui H5 复刻版明日方舟游戏主界面 项目地址: https://gitcode.com/gh_mirrors/ar/arknights-ui arknights-ui是一个基于H5CSS技术的开源项目&#xff0c;它提供…...

RePKG:5个高效技巧助你掌握Wallpaper Engine资源处理与格式转换

RePKG&#xff1a;5个高效技巧助你掌握Wallpaper Engine资源处理与格式转换 【免费下载链接】repkg Wallpaper engine PKG extractor/TEX to image converter 项目地址: https://gitcode.com/gh_mirrors/re/repkg RePKG是一款专注于Wallpaper Engine资源处理的开源工具&…...

NSudo完全指南:轻松获取Windows最高权限的5种方法

NSudo完全指南&#xff1a;轻松获取Windows最高权限的5种方法 【免费下载链接】NSudo [Deprecated, work in progress alternative: https://github.com/M2Team/NanaRun] Series of System Administration Tools 项目地址: https://gitcode.com/gh_mirrors/ns/NSudo NSu…...

AI赋能算法创新:让快马大模型为你的智能车竞赛方案提供灵感

AI赋能算法创新&#xff1a;让快马大模型为你的智能车竞赛方案提供灵感 智能车竞赛一直是技术爱好者展示创新能力的舞台&#xff0c;但面对复杂的赛道和实时控制需求&#xff0c;很多队伍在算法设计上容易陷入瓶颈。最近我在准备比赛时&#xff0c;发现InsCode(快马)平台的AI辅…...

告别多显示器DPI混乱:SetDPI让Windows显示体验重获新生

告别多显示器DPI混乱&#xff1a;SetDPI让Windows显示体验重获新生 【免费下载链接】SetDPI 项目地址: https://gitcode.com/gh_mirrors/se/SetDPI 问题发现&#xff1a;当多显示器成为工作障碍 多显示器用户最常遇到的显示难题是什么&#xff1f;想象这样的场景&…...

效率倍增:用快马生成openclaw一键式部署与配置工具

效率倍增&#xff1a;用快马生成openclaw一键式部署与配置工具 最近在团队协作时遇到了一个头疼的问题&#xff1a;每次新成员加入或者更换开发机&#xff0c;都需要手动部署openclaw环境。这个过程中不仅需要重复下载、解压、配置&#xff0c;还经常因为网络代理、权限等问题…...