线程安全(现象、原理、解决、死锁)

2023-11-10

线程安全

线程不安全现象

黄牛抢票程序

  • 直接上代码,创建了4个线程分别表示4个抢票的,我们知道抢票,肯定是一人一票,不可能存在两个人买的是同一张票,接下来的代码的结果就是线程不安全的现象
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>


#define THREADCOUNT 4

int g_tickets = 100;

void* threadStart(void* arg)
{
  (void)arg; // 防止报错的
  while(1)
  {
    if(g_tickets > 0)
    {
      printf("i am thread %p, i get tickets:%d\n", pthread_self(), g_tickets);
      g_tickets--;
    }
    else
    {
      printf("no tickets\n");
      break;
    }
  }

}
int main()
{
  pthread_t tid[THREADCOUNT];
  for(int i = 0; i < THREADCOUNT; i++)
  {
    int ret = pthread_create(&tid[i], NULL, threadStart, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
  }
  
  for(int i = 0; i < THREADCOUNT; i++)
  {
    pthread_join(tid[i], NULL);
  }

  return 0;
}
  • 观察前两行,两个不同的线程f700和e700拿到的是同一张票,显然这是不合理的
    在这里插入图片描述

线程不安全原理

结论:线程不安全会导致程序结果出现二义性

  • 举例

假设我们现在只有一个cpu,程序中有一个全局变量g_val,初值为10,现在有两个线程A和B要对g_val进行++操作;
线程A获得cpu资源,对全局变量进行++操作,而++操作并非是原子性操作,也就意味着线程A在执行加的过程中有可能会被打断,假设,线程A刚刚从内存中读取g_val保存到寄存器中,就被切换出去了,那么此时,线程A的程序计数器中保存的是下一条指令,上下文信息中(寄存器中)保存的值为10;
切换到线程B,线程B对全局变量g_val执行了++操作,并将结果11回写到内存中,线程B被切换出去;
再次切换到线程A时,线程A恢复现场,继续往下执行,通过上下文信息从寄存器中读到的g_val的值还是10,然后根据程序计数器执行下一条指令,对g_val进行++操作后,也将结果11回写到内存中;
总结:理论上,线程A和线程B都对g_val进行了++操作,g_val的值应该被修改成12,但是上述场景中g_val的值被修改为11。这就是线程不安全

线程不安全怎么解决

互斥
互斥是指同一时间,有且只有一个执行流访问临界资源
保证互斥的方式:互斥锁
同步
同步是在互斥的基础上,解决资源分配不平衡的问题

线程安全相关概念

临界资源
多个线程都能访问到的资源,称之为临界资源
临界区
访问临界资源的代码区域,称之为临界区
互斥
想要保证互斥,我们需要用到互斥锁
互斥锁
互斥锁本身也是一种资源,也就是说,在代码中获取互斥锁的时候也要保证多个线程互斥
本质
在互斥锁内部有一个计数器,也就是互斥量;计数器的值只能为1或0;
当线程获取互斥锁的时候,如果计数器当中的值为0,表示当前线程获取不到互斥锁,即加锁失败,此时一定不能去访问临界资源;
当线程获取互斥锁的时候,如果计数器当中的值为1,表示当前线程获取到互斥锁,即加锁成功,此时就可以执行代码中临界区的代码了
互斥锁中的计数器是如何保证原子性的?
在这里插入图片描述
在获取锁资源的时候(加锁的时候):
1.将寄存器当中的值赋值为0
2.将寄存器当中的值和内存中计数器的值交换
3.判断交换后寄存器当中的值,得出加锁结果
3.1当寄存器当中的值为1时,则表示可以加锁
3.2当寄存器当中的值为0时,则表示不可以加锁

初始化互斥锁变量

动态初始化
锁变量
互斥锁变量的类型为pthread_mutex_t,该类型是一个结构体

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// mutex:传入要进行初始化的锁变量的地址
// attr:互斥锁属性,一般传递NULL,表示采用默认属性

静态初始化

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

PTHREAD_MUTEX_INITIALIZER这个宏定义了一个结构体的值
在这里插入图片描述

加锁

pthread_mutex_lock

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

如果mutex当中计数器当中的值为1,则pthread_mutex_lock接口就返回了,表示加锁成功,同时将计数器当中的值改为0;
如果mutex当中计数器当中的值为0,则pthread_mutex_lock接口就阻塞了,函数不会返回,直到加锁成功

pthread_mutex_trylock

#include <pthread.h>

int pthread_mutex_trylock(pthread_mutex_t *mutex);

该接口是非阻塞接口
如果mutex当中计数器当中的值为1,则加锁成功,函数返回;
如果mutex当中计数器当中的值为0,也会返回,但是加锁失败,一定不能去访问临界资源
注意:一般非阻塞接口都要搭配循环去使用

pthread_mutex_timedlock

#include <pthread.h>
#include <time.h>

int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abs_timeout);

带有超时时间的加锁接口
带有超时时间的加锁接口,意味着当不能立刻获取到锁资源的时候,会等待abs_timeout时间;
如果在abs_timeout时间内加锁成功,立即返回;
如果超过abs_timeout时间,也会返回,但是表示加锁失败;
需要循环加锁

解锁

#include <pthread.h>

int pthread_mutex_unlock(pthread_mutex_t *mutex);

不管是用哪个加锁接口加锁成功的,都可以使用该接口进行解锁;
解锁的时候,会将互斥锁中的计数器的值从0变为1,表示其他线程可以获取该互斥锁

销毁锁

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);

针对动态初始化互斥锁(调用pthread_mutex_init函数进行初始化的互斥锁)

修改黄牛抢票程序

代码中需要关注以下四点:
1.在什么地方对锁进行初始化?
要保证程序中所有线程获取的是同一个互斥锁,所以可以将互斥锁定义为一个全局变量,然后再main函数中进行初始化
2.在哪里加锁?
要访问临界资源时加锁
3.在哪里解锁?
在任何可能导致线程退出的地方都要进行解锁,否则线程带着互斥锁退出了,其他线程会阻塞在获取锁的地方
4.在哪里释放锁?
对临界资源的全部操作结束后(线程全部返回)即可释放

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>


#define THREADCOUNT 4

int g_tickets = 100;
pthread_mutex_t lock_;

void* threadStart(void* arg)
{
  (void)arg; // 防止报错的
  while(1)
  {
    pthread_mutex_lock(&lock_);

    if(g_tickets > 0)
    {
      printf("i am thread %p, i get tickets:%d\n", pthread_self(), g_tickets);
      g_tickets--;
      pthread_mutex_unlock(&lock_);
    }
    else
    {
      printf("no tickets\n");
      pthread_mutex_unlock(&lock_);
      break;
    }
  }

  pthread_mutex_unlock(&lock_);
  return NULL;
}

int main()
{
  pthread_mutex_init(&lock_, NULL);

  pthread_t tid[THREADCOUNT];
  for(int i = 0; i < THREADCOUNT; i++)
  {
    int ret = pthread_create(&tid[i], NULL, threadStart, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
  }
  
  for(int i = 0; i < THREADCOUNT; i++)
  {
    pthread_join(tid[i], NULL);
  }

  pthread_mutex_destroy(&lock_);

  return 0;
}
  • 修改之后,虽然保证了互斥,程序运行结果没有二义性,但是,可以看到可能存在一个线程买好几张票的现象,资源不平衡
    在这里插入图片描述
    基于上边代码的问题,引入下面的同步

同步

同步是为了保证各个线程对临界资源访问的合理性
同步会使用到条件变量
条件变量
本质是一个TCB等待队列+一堆接口
条件变量的接口
初始化 pthread_cond_init

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
// cond:传入条件变量的地址 pthread_cond_t 是条件变量的类型
// attr:条件变量的属性,一般传递NULL,表示采用默认属性

等待 pthread_cond_wait
将调用该接口的线程放到PCB等待队列中

#include <pthread.h>

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

Q:为什么条件变量的等待接口中会有互斥锁?
1.因为同步并不能保证互斥,而保证互斥要用到互斥锁
2.pthread_cond_wait函数中会解锁,并且是先放到PCB等待队列中,然后再解锁
等待接口的内部实现原理
1.将调用pthread_cond_wait函数的线程放到PCB等待队列中
2.解互斥锁
3.等待被唤醒

唤醒 pthread_cond_signal和pthread_cond_broadcast
通知PCB等待队列当中的线程,将其从PCB队列中出队,唤醒该线程

pthread_cond_signal函数是至少唤醒一个线程

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_broadcast函数是将PCB队列当中的所有线程全部唤醒

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);

Q:被唤醒后会做什么?
情况一:拿到互斥锁,pthread_cond_wait函数就返回了
情况二:没有抢到互斥锁,阻塞在pthread_cond_wait函数内部抢锁逻辑的执行流,一旦时间片耗尽,意味着当前线程被切换出来,程序计数器中保存的就是抢锁的指令,上下文信息当中保存的就是寄存器当中的值;当再次拥有CPU时间片之后,从程序计数器和上下文信息当中恢复抢锁逻辑;直到抢锁成功,pthread_cond_wait函数才返回

释放 pthread_cond_destroy

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
  • 代码示例(吃面程序):有两个消费者线程,两个生产者线程;消费者只吃面,生产者只做面
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREADCOUNT 2

int g_bowl = 0;
pthread_mutex_t mutex_;
pthread_cond_t cond_;

void* ConStart(void* arg)
{
  while(1)
  {
    pthread_mutex_lock(&mutex_);
    while(g_bowl <= 0)
    {
      // 放到PCB等待队列
      pthread_cond_wait(&cond_, &mutex_);
    }
    printf("i am consumer %p, i eat %d\n", pthread_self(), g_bowl);
    g_bowl--;
    pthread_mutex_unlock(&mutex_);

    pthread_cond_signal(&cond_);
  }
  return NULL;
}

void* ProStart(void* arg)
{
  while(1)
  {
    pthread_mutex_lock(&mutex_);
    while(g_bowl > 0)
    {
      // 放到PCB等待队列
      pthread_cond_wait(&cond_, &mutex_);
    }
    g_bowl++;
    printf("i am producer %p, i make %d\n", pthread_self(), g_bowl);
    pthread_mutex_unlock(&mutex_);

    pthread_cond_signal(&cond_);
  }
  return NULL;
}

int main()
{
  pthread_mutex_init(&mutex_, NULL);
  pthread_cond_init(&cond_, NULL);

  pthread_t consumer[THREADCOUNT]; 
  pthread_t producer[THREADCOUNT];

  for(int i = 0; i < THREADCOUNT; i++)
  {
    int ret = pthread_create(&consumer[i], NULL, ConStart, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
    
    ret =  pthread_create(&producer[i], NULL, ProStart, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
  }

  for(int i = 0; i < THREADCOUNT; i++)
  {
    pthread_join(consumer[i], NULL);
    pthread_join(producer[i], NULL);
  }
  
  pthread_mutex_destroy(&mutex_);
  pthread_cond_destroy(&cond_);

  return 0;
}
  • 第一个现象:进程会卡住

  • 第二个现象:卡的地方不一定相同
    在这里插入图片描述

  • 查看函数调用栈可以看到从Thread2到Thread5都卡在pthread_cond_wait函数中
    在这里插入图片描述
    上面代码产生的卡住的现象,是因为只有一个条件变量,而通过条件变量唤醒接口出队的线程可能有吃面的线程,也有可能有做面的线程,也有可能全是吃面的,也有可能全是做面的;假设一种极端情况,做面的线程中做好面之后(g_val==1),通知PCB等待队列出队的全部依然是做面的线程,那么做面的线程被唤醒之后就会进入到抢锁逻辑中,但是每次while循环判断都是g_val>0,然后又将做面线程放到PCB等待队列;也就是说线程会卡死在while循环中的pthread_cond_wait函数中

  • 基于上述问题,对吃面程序做修改,就只需给吃面的线程和做面的线程各自一个条件变量(有两个PCB队列)

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define THREADCOUNT 2

int g_bowl = 0;
pthread_mutex_t mutex_;
pthread_cond_t cond_; // 吃面的条件变量
pthread_cond_t makecond_; // 做面的条件变量

void* ConStart(void* arg)
{
  while(1)
  {
    pthread_mutex_lock(&mutex_);
    while(g_bowl <= 0)
    {
      // 放到PCB等待队列
      pthread_cond_wait(&cond_, &mutex_);
    }
    printf("i am consumer %p, i eat %d\n", pthread_self(), g_bowl);
    g_bowl--;
    pthread_mutex_unlock(&mutex_);

    pthread_cond_signal(&makecond_);
  }
  return NULL;
}

void* ProStart(void* arg)
{
  while(1)
  {
    pthread_mutex_lock(&mutex_);
    while(g_bowl > 0)
    {
      // 放到PCB等待队列
      pthread_cond_wait(&makecond_, &mutex_);
    }
    g_bowl++;
    printf("i am producer %p, i make %d\n", pthread_self(), g_bowl);
    pthread_mutex_unlock(&mutex_);

    pthread_cond_signal(&cond_);
  }
  return NULL;
}

int main()
{
  pthread_mutex_init(&mutex_, NULL);
  pthread_cond_init(&cond_, NULL);
  pthread_cond_init(&makecond_, NULL);

  pthread_t consumer[THREADCOUNT]; 
  pthread_t producer[THREADCOUNT];

  for(int i = 0; i < THREADCOUNT; i++)
  {
    int ret = pthread_create(&consumer[i], NULL, ConStart, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
    
    ret =  pthread_create(&producer[i], NULL, ProStart, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
  }

  for(int i = 0; i < THREADCOUNT; i++)
  {
    pthread_join(consumer[i], NULL);
    pthread_join(producer[i], NULL);
  }
  
  pthread_mutex_destroy(&mutex_);
  pthread_cond_destroy(&cond_);
  pthread_cond_destroy(&makecond_);

  return 0;
}

死锁

死锁概念及现象
1.当多个执行流使用同一个互斥锁的时候,有一个执行流获取到了互斥锁之后,但是没有释放互斥锁,导致其他执行流都卡死在加锁的接口当中,我们将这种现象称之为死锁
2.多个执行流,多个互斥锁的情况下,每一个执行流都占有一把互斥锁,但是还想申请对方的互斥锁,这种情况下,就会导致各个执行流都阻塞掉,我们将这种现象称之为死锁

  • 第二种死锁场景模拟代码示例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define THREADCOUNT 1

pthread_mutex_t lock1_;
pthread_mutex_t lock2_;

void* threadStart1(void* arg)
{
  pthread_mutex_lock(&lock1_); 
  sleep(2);
  pthread_mutex_lock(&lock2_);
}

void* threadStart2(void* arg)
{
  pthread_mutex_lock(&lock2_); 
  sleep(2);
  pthread_mutex_lock(&lock1_);
}

int main()
{
  pthread_mutex_init(&lock1_, NULL); 
  pthread_mutex_init(&lock2_, NULL);

  pthread_t tid1[THREADCOUNT], tid2[THREADCOUNT];
  for(int i = 0; i < THREADCOUNT; i++)
  {
    int ret = pthread_create(&tid1[i], NULL, threadStart1, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
    
    ret = pthread_create(&tid2[i], NULL, threadStart2, NULL);
    if(ret < 0)
    {
      perror("pthread_create");
      return -1;
    }
  }

  sleep(3);

  for(int i = 0; i < THREADCOUNT; i++)
  {
    pthread_join(tid1[i], NULL);
    pthread_join(tid2[i], NULL);
  }

  pthread_mutex_destroy(&lock1_); 
  pthread_mutex_destroy(&lock2_);
  return 0;
}
  • 运行结果如下,发现进程卡死不动
    在这里插入图片描述
    调试上边的代码
    调试方法一:不运行
    1.想要调试不要忘了加-g
    2.gdb [可执行程序]
    2.1 l:可以查看代码行号
    2.2 b [行号]:在该行下断点
    2.3 r:让代码运行
    2.4 thread apply all bt:查看各个线程调用堆栈
    2.5 t [线程编号]:跳转到指定编号的线程的调用堆栈
    2.6 f [堆栈编号]:跳转到指定堆栈中
    2.7 p [变量]:打印变量的值
    2.8 q:退出gdb

  • 首先,我在49行下了一个断点

  • r让其运行到49行停止,此时一个线程一定拿到了lock1_,现在想要获取lock2_;一个线程一定拿到了lock2_,现在想要获取lock1_
    在这里插入图片描述

  • thread apply all bt:可以看到各个线程的调用堆栈,Thread 1是主线程,Thread 2和Thread 3是工作线程,可以看到工作线程都阻塞在pthread_mutex_lock函数

  • 我打印了lock1_的值,从_owner可以看到lock1_现在被线程号为27917的线程占用,从现场调用堆栈中可以找到线程号为27917的线程是Thread 2(这里的2我们后边就叫它线程编号);lock2_被线程号为27918的线程(Thread 3)占用
    在这里插入图片描述

  • t 2进入到Thread 2的调用堆栈,然后f 3可以看到代码是在调用获取lock2的加锁中

  • t 3进入到Thread 2的调用堆栈,然后f 3可以看到代码是在调用获取lock1的加锁中
    在这里插入图片描述
    调试方法二:运行时调试
    gdb attach [pid]:将进程附加上gdb
    三种调试:
    1.事前调试:gdb [可执行程序]
    2.事中调试:gdb attach [pid]
    3.事后调试:gdb [可执行程序] [coredump]

死锁的四个必要条件
1.互斥条件
2.请求与保持条件
3.不可剥夺条件
4.循环等待

预防死锁
1.破坏必要条件:破坏请求与保持条件或者循环等待
2.加锁顺序一致
3.不要忘记解锁,在所有可能导致执行流退出的地方都进行解锁

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程安全(现象、原理、解决、死锁) 的相关文章

  • 确定 TCP Listen() 队列中当前积压的连接数

    有没有办法找出currentLinux 上 TCP 套接字上等待 Accept 的连接尝试次数 我想我可以在每个事件循环上点击 EWOULDBLOCK 之前计算成功的 Accept 数量 但我使用的是隐藏这些细节的高级库 Python Tw
  • 选择fasta文件中氨基酸超过300个且“C”出现至少4次的序列

    我有一个包含蛋白质序列的 fasta 文件 我想选择超过 300 个氨基酸且半胱氨酸 C 氨基酸出现超过 4 次的序列 我使用此命令来选择具有超过 300 个 aa 的序列 cat 72hDOWN fasta fasta bioawk c
  • 我想在 Red Hat Linux 服务器中执行 .ps1 powershell 脚本

    我有一个在窗口中执行的 ps1 powershell 脚本 但我的整个数据都在 Linux 服务器中 有什么可能的方法可以让我在红帽服务器中执行 powershell 脚本 powershell脚本是 Clear Host path D D
  • Linux 内核使用的设备树文件 (dtb) 可视化工具? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一个可以图形化表示Linux内核中使用的硬件设备树的工具 我正在尝试了解特定 Arm 芯片组
  • 使用 shell 脚本发送 HTML 邮件

    如何使用 shell 脚本发送 HTML 电子邮件 首先 您需要撰写消息 最低限度由这两个标头组成 MIME Version 1 0 Content Type text html 以及适当的消息正文 p Hello world p 获得后
  • 如何在C(Linux utf8终端)中打印“盒子抽屉”Unicode字符?

    我正在尝试显示 方框图范围 2500 257F 中的 Unicode 字符 它应该是标准 utf8 Unicode 标准 版本 6 2 我根本做不到 我首先尝试使用旧的 ASCII 字符 但 Linux 终端以 utf8 显示 并且没有显示
  • 在 scapy 中通过物理环回发送数据包

    我最近发现了 Scapy 它看起来很棒 我正在尝试查看 NIC 上物理环回模块 存根上的简单流量 但是 Scapy sniff 没有给出任何结果 我正在做的发送数据包是 payload data 10 snf sniff filter ic
  • 在ubuntu中打开spyder

    我想在ubuntu中打开spyder Python IDE 通常我会在 shell 中编写 spyder 它会打开spyder IDE 现在 当我在shell中编写spyder时 它只是换行 什么也没有发生 类似于按 enter 我如何找回
  • MySQL 与 PHP 的连接无法正常工作

    这是我的情况 我正在尝试使用 Apache 服务器上的 PHP 文件连接到 MySQL 数据库 现在 当我从终端运行 PHP 时 我的 PHP 可以连接到 MySQL 数据库 使用 php f file php 但是当我从网页执行它时 它只
  • Linux shell 从用户输入中获取设备 ID

    我正在为一个程序编写安装脚本 该程序需要在其配置中使用 lsusb 的设备 ID 因此我正在考虑执行以下操作 usblist lsusb put the list into a array for each line use the arr
  • 如何在 Linux 和 C 中使用文件作为互斥体?

    我有不同的进程同时访问 Linux 中的命名管道 并且我想让此访问互斥 我知道可以使用放置在共享内存区域中的互斥体来实现这一点 但作为一种家庭作业 我有一些限制 于是 我想到的是对文件使用锁定原语来实现互斥 我做了一些尝试 但无法使其发挥作
  • Crontab 每 5 分钟一次 [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我如何告诉 crontab 每 5 分钟运行一次 但从每小时的第二分钟开始 换句话说 我想在以下时间执行我的脚本minute 5 2 例如 我的脚本应
  • 在 x86 汇编语言中获取文件大小的简单方法

    假设我已经在汇编中打开了一个文件 并且在寄存器 eax 中有该文件的文件句柄 我将如何获取文件的大小 以便为其分配足够的缓冲区空间 我在这里研究了另一个讨论 建议使用sys fstat 28 系统调用来获取文件统计信息但无法实现它 My a
  • 使用 libusb 输出不正确

    我用libusb编写了一个程序 我怀疑输出是否正确 因为所有条目都显示相同的供应商和产品 ID 以下是代码 include
  • 如何并行执行4个shell脚本,我不能使用GNU并行?

    我有4个shell脚本dog sh bird sh cow sh和fox sh 每个文件使用 xargs 并行执行 4 个 wget 来派生一个单独的进程 现在我希望这些脚本本身能够并行执行 由于某些我不知道的可移植性原因 我无法使用 GN
  • Ubuntu Python shebang 线不工作

    无法让 shebang 线在 Ubuntu 中为 python 脚本工作 我每次只收到命令未找到错误 test py usr bin env python print Ran which python usr bin python 在 sh
  • Linux 使用 boost asio 拒绝套接字绑定权限

    我在绑定套接字时遇到问题 并且以用户身份运行程序时权限被拒绝 这行代码会产生错误 acceptor new boost asio ip tcp acceptor io boost asio ip tcp endpoint boost asi
  • awk 在循环中使用时不打印任何内容[重复]

    这个问题在这里已经有答案了 我有一堆使用 file 1 a 1 txt 格式的文件 如下所示 A 1 B 2 C 3 D 4 并使用以下命令添加包含每个文件名称的新列 awk print FILENAME NF t 0 file 1 a 1
  • vmsplice() 和 TCP

    在原来的vmsplice 执行 有人建议 http lwn net Articles 181169 如果您的用户态缓冲区是管道中可容纳的最大页面数的 2 倍 则缓冲区后半部分成功的 vmsplice 将保证内核使用缓冲区的前半部分完成 但事
  • FileOutputStream.close() 中的设备 ioctl 不合适

    我有一些代码可以使用以下命令将一些首选项保存到文件中FileOutputStream 这是我已经写了一千遍的标准代码 FileOutputStream out new FileOutputStream file try BufferedOu

随机推荐

  • 第七篇 硬件内存资源的获取,解析

    硬件资源的获取 解析 1 生成WDF的KMDFhelloWorld程序 2 改写INF文件中硬件ID 3 编译安装 以上三点不重复介绍 下面直接关注更新 增加的代码 在DeviceADD例程中添加 首先是增加即插即用管理 应该对应于WDM的
  • 内外网切换BAT脚本

    1 切换内网脚本 BAT脚本设置IP 子网掩码 网关 DNS echo off cls color 0A Echo Echo 正在修改IP地址和DNS服务器地址 请耐心等待 Echo cmd c netsh interface ip set
  • AntDesign 自定义图片上传前端压缩画质

    为什么压缩图片 应为现在公司没有使用云数据库 从而为了减少服务器的消耗需要将用户上传的图片压缩 前端压缩图片的技术选择 查阅资料发现当下两种压缩的方法 第一种使用工具库实现 npm install image conversion save
  • EXT4.2.0 + common-fileipload1.3 + 大文件上传(可达到20G)

    待续
  • npm 设置淘宝镜像

    查看当前镜像源 npm config get registry 在国外 受网速的影响的影响比较大 https registry npmjs org 1 配置 npm 为淘宝镜像 npm config set registry https r
  • 寒假小复习1

    1 java标识符 1 由数字 字母 构成 其中数字不能放在开头 2 java的关键字不能用为标识符 3 没有长度限制 注意区分大小写 package demo public class Demo public static final d
  • Vue数组对象,数组中多个对象将colorName值相同的合并

    这里使用到了Object values xx reduce prev cur index 方法 首先这个数据源是长这样的 需要的效果是长这样 代码 skuSizeData 是数据源 skuSizePropData 是在此之前我将所有的尺码进
  • QT事件循环与线程

    初次读到这篇文章 译者感觉如沐春风 深刻体会到原文作者是花了很大功夫来写这篇文章的 文章深入浅出 相信仔细读完原文或下面译文的读者一定会有收获 由于原文很长 原文作者的行文思路是从事件循环逐渐延伸到线程使用的讨论 译者因时间受限 暂发表有关
  • RSA简介

    什么是RSA RSA算法是应用最广泛的公钥密码算法 1977年 RSA算法由MIT的罗纳德 李维斯特 Ron Rivest 阿迪 萨莫尔 Adi Shamir 和伦纳德 阿德曼 Leonard Adleman 共同设计 于1978年正式发布
  • ==和equals

    对比的是栈中的值 基本数据类型是变量值 引用类型是堆中内存对象的值 equals object中默认也是采用 进行比较 通常会重写 String已经重写了equals方法 如下实际上是比较两个字符串中每一个字符的内容 public bool
  • 【C语言进阶】从一组数字中,找出只出现过一次的两个数字

    题目描述 有一组数字 只有两个数字出现过一次 其余数字都出现过两次 请找出只出现过一次的数字 举例 数组 1 2 3 4 6 1 2 3 4 8 输出 6 8 思路 这种题目是一种特定类型 形式1 一组数字 只有一个数字出现过一次 其余数字
  • webstrom 断点调试,小白详细步骤篇

    网上找了很久都是七零八落的 要么就是不够通俗易懂故作此文章记录和分享 设置debug调试 编辑器右上角点编辑配置 点击 号选js调试 url填你项目运行的url和端口 以及用于调试的浏览器 添加断点和运行测试调试 这里打上断点 点击右上角d
  • 应用服务器巡检,服务器巡检-常规检查项

    服务器巡检 常规检查项 由会员分享 可在线阅读 更多相关 服务器巡检 常规检查项 3页珍藏版 请在人人文库网上搜索 1 服务器巡检项目经理需要定期对硬件 软件及数据库环境进行检查常规检查项如下 1 服务器部分 l 服务器前面板有无故障灯闪烁
  • 帆软可以不可以生成文件到服务器_番禺街坊注意!微信发送高清大文件不压缩,网友:QQ可以卸载了?...

    刚上新了表情包的微信 又双叒叕出新功能了 这次的功能有点实用 昨天 腾讯微信团队宣布 微信已支持向朋友发送高清视频和图片 且不会被压缩 微信用户点击对话框右下角的 号 右滑点击文件 在上方选择手机相册 就能选择用户想要发送的视频和图片了 目
  • 为什么mybatisplus这么好用,反而用的不多?

    对会用的人来说 mybatis plus的wrapper非常好用 不再需要去关注dao层了 但是这需要一定的学习成本 而且不太符合经典的三层架构思维 对一些老前辈来说完全是违反常识的 很别扭 对他们来说 dao层还是拿在自己手里更踏实 给第
  • 【区块链与密码学】第6-9讲:数字签名算法的可证明安全性

    本课堂内容全部选编自PlatON首席密码学家 武汉大学国家网络安全学院教授 博士生导师何德彪教授的 区块链与密码学 授课讲义 教材及互联网 版权归属其原作者所有 如有侵权请立即与我们联系 我们将及时处理 6 9数字签名算法的可证明安全性 可
  • ResNet详解:ResNet到底在解决什么问题?

    原作者开源代码 https github com KaimingHe deep residual networks 论文 https arxiv org pdf 1512 03385 pdf 1 网络退化问题 在ResNet诞生之前 Ale
  • 2021-11-18 迈向程序猿的第三十一步

    目录 一 工具类的封装 二 ORM 三 Dao层的抽取 四 DateUtils 五 Service业务层 一 工具类的封装 问题 每次进行CRUD操作 都要写一套JDBC 很繁琐 解决方案 将重复的操作 抽取到工具类中封装 1 加载驱动只需
  • ctf.show_web10

  • 线程安全(现象、原理、解决、死锁)

    线程安全 线程不安全现象 黄牛抢票程序 直接上代码 创建了4个线程分别表示4个抢票的 我们知道抢票 肯定是一人一票 不可能存在两个人买的是同一张票 接下来的代码的结果就是线程不安全的现象 include