【Linux】多线程(中)

news2025/1/4 21:23:53

目录

一、线程互斥

1.1 互斥概念

1.2 互斥量mutex

1.3 互斥量相关API

(1)初始化互斥量

(2)销毁互斥量

(3)互斥量加锁和解锁

1.4 互斥量原理

1.5 重入和线程安全

二、死锁

2.1 概念

2.2 造成死锁的必要条件

2.3 死锁的处理方式

三、线程同步

2.1 概念

2.2 条件变量相关API

(1)初始化条件变量

(2)销毁条件变量

(3)等待资源就绪

(4)唤醒等待线程


一、线程互斥

1.1 互斥概念

在前面讲System V信号量的时候

【Linux】进程间通信——System V消息队列和信号量_linux semaphore信号量进程间通信-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/Eristic0618/article/details/142635584?spm=1001.2014.3001.5501我们已经接触过互斥这一概念了,这里我们再重温一下相关的概念

  • 临界资源:多线程执行流共享的资源
  • 临界区:访问临界资源的代码
  • 互斥:任何时刻,有且只有一个执行流进入临界区访问临界资源
  • 原子性:不会被任何调度机制打断的操作,要么未开始要么已完成 

1.2 互斥量mutex

对于线程内部创建的局部变量,其存储在线程私有的栈空间内,因此无法被其他线程所访问

但有时线程还需要访问全局变量等共享资源,如果多个线程同时访问这些共享资源,且没有任何的保护机制,就可能导致问题的发生

例如我们模拟一个抢票的场景:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;

int ticket = 100; //100张票

void* threadRoutine(void* arg) //新线程的例程
{
    string name = (char*)arg;
    while(true)
    {
        if(ticket > 0)
        {
            usleep(1000); //模拟抢票动作
            cout << name << " get ticket:" << ticket << endl;
            ticket--;
        }
        else
            break;
    }
    return nullptr;
}

int main()
{
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, threadRoutine, (void*)"Thread 1");
    pthread_create(&t2, nullptr, threadRoutine, (void*)"Thread 2");
    pthread_create(&t3, nullptr, threadRoutine, (void*)"Thread 3");
    pthread_create(&t4, nullptr, threadRoutine, (void*)"Thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);
    return 0;
}

运行结果:

可以看到我们的票数到最后已经变为了一个非法的值,说明在不加保护的场景下,多线程竞争式的访问临界资源可能导致问题。为什么?

  • 线程在if判断为真后进入临界区,这个过程中代码可以并发的切换到其他的进程
  • usleep模拟我们的抢票过程,即便只有1000微秒,对于CPU而言也是一个十分漫长的时间了。在这个过程中可能有很多个线程也会进入到临界区中
  • 减少票数的动作本身也不是一个原子操作,看似只有一条代码,实际对应了三条汇编指令

多个因素导致了多个线程并发进行“抢票”动作时,票数最后变为了负数

要解决这个问题,就必须保证多个线程互斥的进行抢票动作,即同一时刻有且只能有一个线程进入代码临界区访问临界资源。如何做到?我们需要一把“锁”,即互斥量mutex

临界区就像一个小房间,一开始房间的门没有锁,于是大家都一拥而上进入房间。有了互斥量,房门就上了锁,并且同一时刻只允许一个线程能够开锁,线程从房间出来后就要把锁的钥匙归还

1.3 互斥量相关API

(1)初始化互斥量

我们可以通过两种方式初始化互斥量:

  • 静态初始化
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

使用名为PTHREAD_MUTEX_INITIALIZER的宏,在创建互斥量的同时对其进行初始化

  • 动态初始化

使用pthread_mutex_init函数对互斥量进行初始化

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);

其中mutex为要初始化的互斥量,attr设置为nullptr即可,代表使用默认的互斥锁属性

(2)销毁互斥量

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutex_destroy函数用于销毁一个互斥量,其中:

  • 使用静态初始化方式的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保不会再有线程对该互斥量加锁

(3)互斥量加锁和解锁

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

调用pthread_mutex_lock函数的线程将会尝试对目标互斥量加锁

  • 此时互斥量处于未锁状态,那么线程能成功锁定该互斥量
  • 如果互斥量已被其他线程锁定或线程竞争互斥量失败,那么线程就会陷入阻塞并等待互斥量解锁

持有锁的线程调用pthread_mutex_unlock函数后可以对互斥量解锁

将互斥锁引入我们上面的售票系统后:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;

int ticket = 100; //100张票
pthread_mutex_t lock; //互斥锁

void* threadRoutine(void* arg) //新线程的例程
{
    string name = (char*)arg;
    while(true)
    {
        pthread_mutex_lock(&lock); //互斥量加锁
        if (ticket > 0)
        {
            usleep(1000); //模拟抢票动作
            cout << name << " get ticket:" << ticket << endl;
            ticket--;
            pthread_mutex_unlock(&lock); //互斥量解锁
        }
        else
        {
            pthread_mutex_unlock(&lock); //互斥量解锁
            break;
        }
    }
    return nullptr;
}

int main()
{
    pthread_mutex_init(&lock, nullptr); //初始化互斥锁

    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, threadRoutine, (void*)"Thread 1");
    pthread_create(&t2, nullptr, threadRoutine, (void*)"Thread 2");
    pthread_create(&t3, nullptr, threadRoutine, (void*)"Thread 3");
    pthread_create(&t4, nullptr, threadRoutine, (void*)"Thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    pthread_mutex_destroy(&lock); 
    return 0;
}

运行结果:

引入互斥量后,会发现只有一个线程不停执行抢票操作,原因:

解锁后的线程离互斥锁”最近“,其竞争能力相比其他线程更强,因此能够更快对互斥量上锁,导致其他线程一直处于阻塞状态

在实际情况下,我们抢完票后也不会立即抢下一张票,在这过程中需要一个时间窗口。因此我们在线程解锁后可以让其睡眠一段时间,模拟抢完票后的动作

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;

int ticket = 100; //100张票
pthread_mutex_t lock; //互斥锁

void* threadRoutine(void* arg) //新线程的例程
{
    string name = (char*)arg;
    while(true)
    {
        pthread_mutex_lock(&lock); //互斥量加锁
        if (ticket > 0)
        {
            usleep(1000); //模拟抢票动作
            cout << name << " get ticket:" << ticket << endl;
            ticket--;
            pthread_mutex_unlock(&lock); //互斥量解锁
        }
        else
        {
            pthread_mutex_unlock(&lock); //互斥量解锁
            break;
        }
        usleep(10); //模拟抢完票后的动作
    }
    return nullptr;
}

int main()
{
    pthread_mutex_init(&lock, nullptr); //初始化互斥锁

    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, nullptr, threadRoutine, (void*)"Thread 1");
    pthread_create(&t2, nullptr, threadRoutine, (void*)"Thread 2");
    pthread_create(&t3, nullptr, threadRoutine, (void*)"Thread 3");
    pthread_create(&t4, nullptr, threadRoutine, (void*)"Thread 4");

    pthread_join(t1, nullptr);
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);
    pthread_join(t4, nullptr);

    pthread_mutex_destroy(&lock); 
    return 0;
}

运行结果:

对于这种纯互斥环境,如果对锁的分配不够合理,就可能导致其他线程的饥饿问题

在我们的视角看来,线程就像排着队申请互斥量,解锁后的线程不能让它能够立刻申请锁,而是排到队列尾部等待。让多个线程按照一定的顺序来获取互斥量,这也是一种同步。

互斥锁要保护临界资源的安全,首先要确保自己是线程安全的。锁本身也是共享资源,因此加锁和解锁本身就必须得是原子性的操作。

并且当线程持有锁在访问临界区时如果被切换走,也必须要保证其他线程无法进入临界区。这些功能是如何做到的?

1.4 互斥量原理

互斥量加锁和解锁的过程都需要多条汇编语句,如何保证其原子性? 

首先看下互斥量加锁的伪代码

lock:
    movb $0, %al
    xchgb %al, mutex
    if(al寄存器的内容 > 0)
        return 0;
    else
        线程挂起等待;
    goto lock;

我们可以把互斥量看作一个整型变量,为1时代表未加锁状态,为0时代表已经被加锁

初始互斥量的值为1,首先线程执行第一条汇编语句时把al寄存器的值变为0        

第二步是重点:将互斥量mutex中的1与al寄存器中的0交换。哪个线程先执行到这条汇编语句,哪个线程才真正的对互斥量上了锁

如果某个线程被切换时寄存器al已经和互斥锁交换拿到了这个1,则会将al寄存器中的数据交换到自己的硬件上下文中带走,这个唯一的1就被该线程所私有。线程被切换走后al寄存器和锁中都没有1,这个1就像一把唯一的钥匙,再没有第二个线程能够获得这个1了。

后续就是通过判断al寄存器的值是否为1来判断线程是否竞争锁成功,如果为1则返回0代表加锁成功,不为1则加锁失败挂起等待

所以,虽然加锁的过程有多条汇编语句,但只有第二条决定一个线程能否申请到锁,因此是原子的

然后是互斥量解锁:

unlock:
    movb $1, mutex
    唤醒被挂起的线程;
    return 0;

要对一个互斥量解锁,只需要再把mutex的值变为1即可

虽然原本持有锁的线程的al寄存器值仍为1,但当该线程再次申请锁时会首先将al寄存器的值变为0,因此不需要在解锁时额外对al寄存器进行处理

1.5 重入和线程安全

关于重入和线程安全的概念:

  • 线程安全:多个线程并发执行同一段代码时,不会出现数据不一致问题。多线程访问全局变量或静态变量且没有保护机制的情况就是线程不安全的情况
  • 重入:当前执行流未调用完某个函数时,有其他执行流再次进入该函数就称为重入。一个函数在被重入的情况下运行结果不会出现问题就称为可重入函数,否则是不可重入函数

常见线程不安全的情况:

  • 不保护共享变量的函数
  • 调用过程中状态会发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

常见不可重入的情况:

  • 调用了malloc/free函数
  • 调用了标准I/O库函数
  • 可重入函数内部使用了静态的数据结构

可重入与线程安全相关概念:

  • 可重入函数是线程安全函数的一种,一个函数是可重入的,那么也是线程安全的
  • 不可重入函数不能被多个线程同时调用,否则可能引发线程安全问题


二、死锁

2.1 概念

死锁通常是因为两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象  

像这样,每辆车都在等待对方先走,自己才能继续向前移动,最后导致所有的车都走不了

2.2 造成死锁的必要条件

系统中的资源分为两种: 

可剥夺性资源:线程在获取资源后,该资源可以被其他进程或者系统剥夺,例如CPU或主存

不可剥夺性资源:系统将该类型的资源分配给某个进程后,不能强行的回收

只有不可剥夺性资源才会造成死锁,当系统中的不可剥夺性资源的数量无法满足多个线程的需求,线程在争夺这些资源时出现阻塞现象,造成死锁

  • 互斥条件:任何时间下一个资源只能被一个执行流调用(一个车道只能走一辆车)
  • 请求与保持条件:一个执行流因请求资源时阻塞等待,不会释放自己已有的资源(车被堵住后不会倒车)
  • 不剥夺条件:线程不能强行剥夺其他线程已获取的资源(不能撞车)
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系(如上图)

2.3 死锁的处理方式

  • 破坏死锁的四个必要条件,例如将资源换成允许共享使用的资源
  • 让线程以安全的序列推进
  • 避免死锁的算法,如银行家算法
  • 死锁检测算法


三、线程同步

2.1 概念

线程同步:在保证数据安全的前提下,让多个线程按照某种特定的顺序访问临界资源,从而有效避免线程的饥饿问题

多个线程互斥访问某个变量时,可能有些情况下变量的条件不满足线程的调用条件,需要等待变量符合条件后才能继续按顺序同步访问该变量。

例如超市没货了,消费者们就需要按顺序等待补货,当有货后超市再依次通知消费者

对于上述情况,就需要用到条件变量

2.2 条件变量相关API

条件变量实际上就是等待队列+通知机制,线程在等待某个资源时被依次加入等待队列中,当资源就绪时通知机制就依次唤醒队列中正在等待的线程

(1)初始化条件变量

  • 静态
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 动态 
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,
                      const pthread_condattr_t *restrict attr);

cond为要初始化的条件变量,attr设置为nullptr即可

(2)销毁条件变量

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

(3)等待资源就绪

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex);

pthread_cond_wait函数用于将当前线程加入条件变量的等待队列中。其中cond为待加入的条件变量,mutex是互斥锁

通过pthread_cond_wait函数的参数我们可以了解到,条件变量必须和锁一起使用,为什么?

在条件变量的使用场景中,往往涉及到多线程访问临界资源的情况,因此我们需要使用互斥锁保证线程安全。

因此就可能出现这样一种情况:

当某个线程持有锁要访问共享资源时,资源未就绪条件不满足,线程需要加入条件变量中等待(但是线程还拿着锁呢!),也就是说在pthread_cond_wait函数中,我们一定需要对线程进行解锁!

这就是为什么参数中包含互斥锁

当临界资源未就绪时让线程等待,因此我们在判断是否需要等待前一定要对临界资源进行判断,而判断也是访问临界资源,要保证线程安全,一定要在加锁之后进行判断

(4)唤醒等待线程

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

其中,pthread_cond_signal用于唤醒一个线程,pthread_cond_broadcast用于唤醒所有线程 

互斥锁和条件变量的经典应用:生产者消费者模型,在下一篇中会提到,这里我们先快速实现一个简单的生产消费模型

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;

#define MAX 5 // 货物最大容量

int goods = 0;       // 货物
pthread_mutex_t lock; // 互斥锁
pthread_cond_t cond;  // 条件变量

void *consumerRoutine(void *arg) // 消费者
{
    while (true)
    {
        pthread_mutex_lock(&lock);           // 加锁
        if (goods == 0)                      // 无货时
            pthread_cond_wait(&cond, &lock); // 等待
        goods--;                             // 消费
        cout << "consuming a goods" << endl;
        pthread_cond_signal(&cond);  // 唤醒等待线程
        pthread_mutex_unlock(&lock); // 解锁
    }
    return nullptr;
}

void *producerRoutine(void *arg) // 生产者
{
    while (true)
    {
        pthread_mutex_lock(&lock);           // 加锁
        if (goods == MAX)                    // 容量已满
            pthread_cond_wait(&cond, &lock); // 等待
        goods++;                             // 生产
        cout << "produce a goods" << endl;
        pthread_cond_signal(&cond);  // 唤醒等待线程
        pthread_mutex_unlock(&lock); // 解锁
    }
    return nullptr;
}

int main()
{
    pthread_mutex_init(&lock, nullptr); // 初始化互斥锁
    pthread_cond_init(&cond, nullptr);  // 初始化条件变量

    pthread_t c, p;
    pthread_create(&c, nullptr, consumerRoutine, nullptr);
    pthread_create(&p, nullptr, producerRoutine, nullptr);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果:

 

完.

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

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

相关文章

【数字图像处理+MATLAB】基于 Sobel 算子计算图像梯度并进行边缘增强:使用 imgradientxy 函数

引言 在图像处理中&#xff0c;边缘通常是图像中像素强度变化最大的地方&#xff0c;这种变化可以通过计算图像的梯度来量化。梯度是一个向量&#xff0c;它的方向指向像素强度增加最快的方向&#xff0c;它的大小&#xff08;或者说幅度&#xff09;表示像素强度增加的速度。…

Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解

title: Nuxt.js 应用中的 schema:beforeWrite 事件钩子详解 date: 2024/11/14 updated: 2024/11/14 author: cmdragon excerpt: schema:beforeWrite 钩子是 Vite 提供的一个功能强大的生命周期钩子,允许开发者在 JSON Schema 被写入之前执行自定义操作。利用这个钩子,您可以…

k8s服务内容滚动升级以及常用命令介绍

查看K8S集群所有的节点信息 kubectl get nodes 删除K8S集群中某个特定节点 kubectl delete nodes/10.0.0.123 获取K8S集群命名空间 kubectl get namespace 获取K8S所有命名空间的那些部署 kubectl get deployment --all-namespaces 创建命名空间 web界面上看到的效果,但是…

MinIo在Ubantu和Java中的整合

1.MinIo在Ubantu中的部署 首先准备好一台已经安装好Ubantu系统的服务器 MinIO是一个开源的对象存储服务器&#xff0c;兼容Amazon S3&#xff0c;性能卓越&#xff0c;适合存储非结构化数据&#xff0c;例如照片、视频、日志文件、备份和容器镜像等。 1&#xff1a;更新系统…

设计模式-参考的雷丰阳老师直播课

一般开发中使用的模式为模版模式策略模式组合&#xff0c;模版用来定义骨架&#xff0c;策略用来实现细节。 模版模式 策略模式 与模版模式特别像&#xff0c;模版模式会定义好步骤定义好框架&#xff0c;策略模式定义小细节 入口类 使用模版模式策略模式开发支付 以上使用…

【LeetCode】【算法】53. 最大子数组和

LeetCode 53. 最大子数组和 题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。子数组是数组中的一个连续部分。 思路 思路&#xff1a;动态规划秒了 具体递推式如…

供应SW6301V单C口多协议升降压移动电源IC

1. 概述 SW6301V 是一款高集成度的单 C 口多协议升降压移动电源 SOC。集成双向升降压控制器&#xff0c;支持 2~6 节 电池串联&#xff0c;提供 100W 功 率 输 入 输 出 &#xff1b; 支 持 C 口 快 充 输入输出 &#xff1b; 支 持UFCS/PPS/PD/SVOOC/VOOC/SCP/FCP/QC/AFC/BC…

C++常用的新特性-->day06

时间间隔duration duration表示一段时间间隔&#xff0c;用来记录时间长度&#xff0c;可以表示几秒、几分钟、几个小时的时间间隔。duration的原型如下 // 定义于头文件 <chrono> template<class Rep,class Period std::ratio<1> > class duration;Rep&…

Cyberchef配合Wireshark提取并解析TCP/FTP流量数据包中的文件

前一篇文章中讲述了如何使用cyberchef提取HTTP/TLS数据包中的文件,详见《Cyberchef配合Wireshark提取并解析HTTP/TLS流量数据包中的文件》,链接这里,本文讲述下如何使用cyberchef提取FTP/TCP数据包中的文件。 FTP 是最为常见的文件传输协议,和HTTP协议不同的是FTP协议传输…

性能面向下一代PCIe Gen 5,G991B322HR、G99L12312HR 安费诺ExtremePort™ Swift连接器支持内部I/O应用

前言 为了在网络设备和服务器上提供更高速度和更小尺寸的解决方案&#xff0c;Amphenol开发了ExtremePort™ Swift连接器&#xff0c;适用于PCIe Gen5 NRZ 32GT/s、UPI 2.0 24GT/s、24Gb/s SAS信号。 G991B322HR G9912312HR G9912322HR G9914312HR G991B312HR G991C312HR G99…

IDEA调整警告级别【IntelliJ IDEA 2024.2.0.1】

文章目录 目前现状鼠标悬停&#xff0c;选择配置筛选 > 取消选择OK效果 目前现状 需要把提示改成只要显示error的5个 鼠标悬停&#xff0c;选择配置 筛选 > 取消选择 OK 效果

【二叉搜素树】——LeetCode二叉树问题集锦:6个实用题目和解题思路

文章目录 计算布尔二叉树的值求根节点到叶节点的数字之和二叉树剪枝验证二叉搜索树二叉搜索树中第K小的元素二叉树的所有路径 计算布尔二叉树的值 解题思路&#xff1a; 这是一个二叉树的布尔评估问题。树的每个节点包含一个值&#xff0c;其中叶子节点值为 0 或 1&#xff0…

windows下QT5.12.11使用MSVC编译器编译mysql驱动并使用详解

1、下载mysql开发库,后面驱动编译的时候需要引用到,下载地址:mysql开发库下载 2、使用everything搜索:msvc-version.conf,用记事本打开,添加:QMAKE_MSC_VER=1909。不然msvc下的mysql源码加载不上。

Isaac Sim+SKRL机器人并行强化学习

目录 Isaac Sim介绍 OmniIssacGymEnvs安装 SKRL安装与测试 基于UR5的机械臂Reach强化学习测评 机器人控制 OMNI GYM环境编写 SKRL运行文件 训练结果与速度对比 结果分析 运行体验与建议 Isaac Sim介绍 Isaac Sim是英伟达出的一款机器人仿真平台&#xff0c;适用于做机…

删库跑路,启动!

起因&#xff1a;这是一个悲伤的故事&#xff0c;在抓logcat时 device待机自动回根目录了&#xff0c;而题主对当前路径的印象还停留在文件夹下&#xff0c;不小心在根目录执行了rm -rf * … 所以&#xff0c;这是个悲伤的故事&#xff0c;东西全没了…device也黑屏了&#xff…

CSS的综合应用例子(网页制作)

这是html的一些最基本内容的代码&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <t…

SobarQube实现PDF报告导出

文章目录 前言一、插件配置二、使用步骤1.新生成一个Token2.将拷贝的Token加到上文中执行的命令中3.查看报告 三、友情提示总结 前言 这篇博文是承接此文 .Net项目在Windows中使用sonarqube进行代码质量扫描的详细操作配置 描述如何导出PDF报告 众所周知&#xff0c;导出PDF功…

力扣-Mysql-3328-查找每个州的城市 II(中等)

一、题目来源 3328. 查找每个州的城市 II - 力扣&#xff08;LeetCode&#xff09; 二、数据表结构 表&#xff1a;cities ---------------------- | Column Name | Type | ---------------------- | state | varchar | | city | varchar | ----------------…

curl命令提交大json

有个客户需要提交一个4M左右的pdf&#xff0c;接口里传的是pdf字节流base64编码后的字符串。 直接curl -XPOST -d json串 api接口会报 参数过长报错Argument list too long 网上搜了下解决方案把json串放到文本里然后通过json.txt引入参数 这一试不要紧&#xff0c;差点儿导致…

gitlab和jenkins连接

一&#xff1a;jenkins 配置 安装gitlab插件 生成密钥 id_rsa 要上传到jenkins&#xff0c;id_rsa.pub要上传到gitlab cat /root/.ssh/id_rsa 复制查看的内容 可以看到已经成功创建出来了对于gitlab的认证凭据 二&#xff1a;配置gitlab cat /root/.ssh/id_rsa.pub 复制查…