Linux 线程创建

2023-11-08

如何创建线程?

看来多线程还是有很多好处的。接下来我们来看一下,如何使用线程来干一件大事。

假如说,现在我们有 N 个非常大的视频需要下载,一个个下载需要的时间太长了。按照刚才的思路,我们可以拆分成 N 个任务,分给 N 个线程各自去下载。

我们知道,进程的执行是需要项目执行计划书的,那线程是一个项目小组,这个小组也应该有自己的项目执行计划书,也就是一个函数。我们将要执行的子任务放在这个函数里面,比如上面的下载任务。

这个函数参数是 void 类型的指针,用于接收任何类型的参数。我们就可以将要下载的文件的文件名通过这个指针传给它。

为了方便,我将代码整段都贴在这里,这样你把下面的代码放在一个文件里面就能成功编译。

当然,这里我们不是真的下载这个文件,而仅仅打印日志,并生成一个一百以内的随机数,作为下载时间返回。这样,每个子任务干活的同时在喊:“我正在下载,终于下载完了,用了多少时间。”

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
 
#define NUM_OF_TASKS 5
 
void *downloadfile(void *filename)
{
   printf("I am downloading the file %s!\n", (char *)filename);
   sleep(10);
   long downloadtime = rand()%100;
   printf("I finish downloading the file within %d minutes!\n", downloadtime);
   pthread_exit((void *)downloadtime);
}
 
int main(int argc, char *argv[])
{
   char files[NUM_OF_TASKS][20]={"file1.avi","file2.rmvb","file3.mp4","file4.wmv","file5.flv"};
   pthread_t threads[NUM_OF_TASKS];
   int rc;
   int t;
   int downloadtime;
 
   pthread_attr_t thread_attr;
   pthread_attr_init(&thread_attr);
   pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_JOINABLE);
 
   for(t=0;t<NUM_OF_TASKS;t++){
     printf("creating thread %d, please help me to download %s\n", t, files[t]);
     rc = pthread_create(&threads[t], &thread_attr, downloadfile, (void *)files[t]);
     if (rc){
       printf("ERROR; return code from pthread_create() is %d\n", rc);
       exit(-1);
     }
   }
 
   pthread_attr_destroy(&thread_attr);
 
   for(t=0;t<NUM_OF_TASKS;t++){
     pthread_join(threads[t],(void**)&downloadtime);
     printf("Thread %d downloads the file %s in %d minutes.\n",t,files[t],downloadtime);
   }
 
   pthread_exit(NULL);
}

一个运行中的线程可以调用 pthread_exit 退出线程。这个函数可以传入一个参数转换为 (void *) 类型。这是线程退出的返回值。

接下来,我们来看主线程。在这里面,我列了五个文件名。接下来声明了一个数组,里面有五个 pthread_t 类型的线程对象。

接下来,声明一个线程属性 pthread_attr_t。我们通过 pthread_attr_init 初始化这个属性,并且设置属性 PTHREAD_CREATE_JOINABLE。这表示将来主线程程等待这个线程的结束,并获取退出时的状态。

接下来是一个循环。对于每一个文件和每一个线程,可以调用 pthread_create 创建线程。一共有四个参数,第一个参数是线程对象,第二个参数是线程的属性,第三个参数是线程运行函数,第四个参数是线程运行函数的参数。主线程就是通过第四个参数,将自己的任务派给子线程。

任务分配完毕,每个线程下载一个文件,接下来主线程要做的事情就是等待这些子任务完成。当一个线程退出的时候,就会发送信号给其他所有同进程的线程。有一个线程使用 pthread_join 获取这个线程退出的返回值。线程的返回值通过 pthread_join 传给主线程,这样子线程就将自己下载文件所耗费的时间,告诉给主线程。

好了,程序写完了,开始编译。多线程程序要依赖于 libpthread.so。

gcc download.c -lpthread

编译好了,执行一下,就能得到下面的结果。

# ./a.out
creating thread 0, please help me to download file1.avi
creating thread 1, please help me to download file2.rmvb
I am downloading the file file1.avi!
creating thread 2, please help me to download file3.mp4
I am downloading the file file2.rmvb!
creating thread 3, please help me to download file4.wmv
I am downloading the file file3.mp4!
creating thread 4, please help me to download file5.flv
I am downloading the file file4.wmv!
I am downloading the file file5.flv!
I finish downloading the file within 83 minutes!
I finish downloading the file within 77 minutes!
I finish downloading the file within 86 minutes!
I finish downloading the file within 15 minutes!
I finish downloading the file within 93 minutes!
Thread 0 downloads the file file1.avi in 83 minutes.
Thread 1 downloads the file file2.rmvb in 86 minutes.
Thread 2 downloads the file file3.mp4 in 77 minutes.
Thread 3 downloads the file file4.wmv in 93 minutes.
Thread 4 downloads the file file5.flv in 15 minutes.

这里我们画一张图总结一下,一个普通线程的创建和运行过程。

线程的数据

线程可以将项目并行起来,加快进度,但是也带来的负面影响,过程并行起来了,那数据呢?

我们把线程访问的数据细分成三类。下面我们一一来看。

第一类是线程栈上的本地数据,比如函数执行过程中的局部变量。前面我们说过,函数的调用会使用栈的模型,这在线程里面是一样的。只不过每个线程都有自己的栈空间。

栈的大小可以通过命令 ulimit -a 查看,默认情况下线程栈大小为 8192(8MB)。我们可以使用命令 ulimit -s 修改。

对于线程栈,可以通过下面这个函数 pthread_attr_t,修改线程栈的大小。

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

主线程在内存中有一个栈空间,其他线程栈也拥有独立的栈空间。为了避免线程之间的栈空间踩踏,线程栈之间还会有小块区域,用来隔离保护各自的栈空间。一旦另一个线程踏入到这个隔离区,就会引发段错误。

第二类数据就是在整个进程里共享的全局数据。例如全局变量,虽然在不同进程中是隔离的,但是在一个进程中是共享的。如果同一个全局变量,两个线程一起修改,那肯定会有问题,有可能把数据改的面目全非。这就需要有一种机制来保护他们,比如你先用我再用。这一节的最后,我们专门来谈这个问题。

那线程能不能像进程一样,也有自己的私有数据呢?如果想声明一个线程级别,而非进程级别的全局变量,有没有什么办法呢?虽然咱们都是一个大组,分成小组,也应该有点隐私。这就是第三类数据,线程私有数据(Thread Specific Data),可以通过以下函数创建:

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))

可以看到,创建一个 key,伴随着一个析构函数。

key 一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往 key 中填入不同的值,这就相当于提供了一个同名而不同值的全局变量。我们可以通过下面的函数设置 key 对应的 value。

int pthread_setspecific(pthread_key_t key, const void *value)

我们还可以通过下面的函数获取 key 对应的 value。

void *pthread_getspecific(pthread_key_t key)

而等到线程退出的时候,就会调用析构函数释放 value。

数据的保护

接下来,我们来看共享的数据保护问题。

我们先来看一种方式,Mutex,全称 Mutual Exclusion,中文叫互斥。我这里构建了一个“转账”的场景。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
 
#define NUM_OF_TASKS 5
 
int money_of_tom = 100;
int money_of_jerry = 100;
// 第一次运行去掉下面这行
pthread_mutex_t g_money_lock;
 
void *transfer(void *notused)
{
  pthread_t tid = pthread_self();
  printf("Thread %u is transfering money!\n", (unsigned int)tid);
  // 第一次运行去掉下面这行
  pthread_mutex_lock(&g_money_lock);
  sleep(rand()%10);
  money_of_tom+=10;
  sleep(rand()%10);
  money_of_jerry-=10;
  // 第一次运行去掉下面这行
  pthread_mutex_unlock(&g_money_lock);
  printf("Thread %u finish transfering money!\n", (unsigned int)tid);
  pthread_exit((void *)0);
}
 
int main(int argc, char *argv[])
{
  pthread_t threads[NUM_OF_TASKS];
  int rc;
  int t;
  // 第一次运行去掉下面这行
  pthread_mutex_init(&g_money_lock, NULL);
 
  for(t=0;t<NUM_OF_TASKS;t++){
    rc = pthread_create(&threads[t], NULL, transfer, NULL);
    if (rc){
      printf("ERROR; return code from pthread_create() is %d\n", rc);
      exit(-1);
    }
  }
  
  for(t=0;t<100;t++){
    // 第一次运行去掉下面这行
    pthread_mutex_lock(&g_money_lock);
    printf("money_of_tom + money_of_jerry = %d\n", money_of_tom + money_of_jerry);
    // 第一次运行去掉下面这行
    pthread_mutex_unlock(&g_money_lock);
  }
  // 第一次运行去掉下面这行
  pthread_mutex_destroy(&g_money_lock);
  pthread_exit(NULL);
}

这里说,有两个员工 Tom 和 Jerry,公司食堂的饭卡里面各自有 100 元,并行启动 5 个线程,都是 Jerry 转 10 元给 Tom,主线程不断打印 Tom 和 Jerry 的资金之和。按说,这样的话,总和应该永远是 200 元。

在上面的程序中,我们先去掉 mutex 相关的行,就像注释里面写的那样。在没有锁的保护下,在 Tom 的账户里面加上 10 元,在 Jerry 的账户里面减去 10 元,这不是一个原子操作。我们来编译一下。

gcc mutex.c -lpthread

然后运行一下,就看到了下面这样的结果。

[root@deployer createthread]# ./a.out
Thread 508479232 is transfering money!
Thread 491693824 is transfering money!
Thread 500086528 is transfering money!
Thread 483301120 is transfering money!
Thread 516871936 is transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 220
money_of_tom + money_of_jerry = 220
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 240
Thread 483301120 finish transfering money!
money_of_tom + money_of_jerry = 240
Thread 508479232 finish transfering money!
Thread 500086528 finish transfering money!
money_of_tom + money_of_jerry = 220
Thread 516871936 finish transfering money!
money_of_tom + money_of_jerry = 210
money_of_tom + money_of_jerry = 210
Thread 491693824 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200

可以看到,中间有很多状态不正确,比如两个人的账户之和出现了超过 200 的情况,也就是 Tom 转入了,Jerry 还没转出。

接下来我们在上面的代码里面,加上 mutex,然后编译、运行,就得到了下面的结果。

 
[root@deployer createthread]# ./a.out
Thread 568162048 is transfering money!
Thread 576554752 is transfering money!
Thread 551376640 is transfering money!
Thread 542983936 is transfering money!
Thread 559769344 is transfering money!
Thread 568162048 finish transfering money!
Thread 576554752 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
Thread 542983936 finish transfering money!
Thread 559769344 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
Thread 551376640 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200

这个结果就正常了。两个账号之和永远是 200。这下你看到锁的作用了吧?

使用 Mutex,首先要使用 pthread_mutex_init 函数初始化这个 mutex,初始化后,就可以用它来保护共享变量了。

pthread_mutex_lock() 就是去抢那把锁的函数,如果抢到了,就可以执行下一行程序,对共享变量进行访;如果没抢到,就被阻塞在那里等待。

如果不想被阻塞,可以使用 pthread_mutex_trylock ()去抢那把锁,如果抢到了,就可以执行下一行程序,对共享变量进行访问;如果没抢到,不会被阻塞,而是返回一个错误码。

当共享数据访问结束了,别忘了使用 pthread_mutex_unlock() 释放锁,让给其他人使用,最终调用 pthread_mutex_destroy() 销毁掉这把锁。

这里我画个图,总结一下 Mutex 的使用流程。

在使用 Mutex 的时候,有个问题是如果使用 pthread_mutex_lock(),那就需要一直在那里等着。如果是 pthread_mutex_trylock(),就可以不用等着,去干点儿别的,但是我怎么知道什么时候回来再试一下,是不是轮到我了呢?能不能在轮到我的时候,通知我一下呢?

这其实就是条件变量,也就是说如果没事儿,就让大家歇着,有事儿了就去通知,别让人家没事儿就来问问,浪费大家的时间。

但是当它接到了通知,来操作共享资源的时候,还是需要抢互斥锁,因为可能很多人都受到了通知,都来访问了,所以条件变量和互斥锁是配合使用的

我这里还是用一个场景给你解释。

你这个老板,招聘了三个员工,但是你不是有了活才去招聘员工,而是先把员工招来,没有活的时候员工需要在那里等着,一旦有了活,你要去通知他们,他们要去抢活干(为啥要抢活?因为有绩效呀!),干完了再等待,你再有活,再通知他们。

具体的样例代码我也放在这里。你可以直接编译运行。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
 
#define NUM_OF_TASKS 3
#define MAX_TASK_QUEUE 11
 
char tasklist[MAX_TASK_QUEUE]="ABCDEFGHIJ";
int head = 0;
int tail = 0;
 
int quit = 0;
 
pthread_mutex_t g_task_lock;
pthread_cond_t g_task_cv;
 
void *coder(void *notused)
{
  pthread_t tid = pthread_self();
 
  while(!quit){
 
    pthread_mutex_lock(&g_task_lock);
    while(tail == head){
      if(quit){
        pthread_mutex_unlock(&g_task_lock);
        pthread_exit((void *)0);
      }
      printf("No task now! Thread %u is waiting!\n", (unsigned int)tid);
      pthread_cond_wait(&g_task_cv, &g_task_lock);
      printf("Have task now! Thread %u is grabing the task !\n", (unsigned int)tid);
    }
    char task = tasklist[head++];
    pthread_mutex_unlock(&g_task_lock);
    printf("Thread %u has a task %c now!\n", (unsigned int)tid, task);
    sleep(5);
    printf("Thread %u finish the task %c!\n", (unsigned int)tid, task);
  }
 
  pthread_exit((void *)0);
}
 
int main(int argc, char *argv[])
{
  pthread_t threads[NUM_OF_TASKS];
  int rc;
  int t;
 
  pthread_mutex_init(&g_task_lock, NULL);
  pthread_cond_init(&g_task_cv, NULL);
 
  for(t=0;t<NUM_OF_TASKS;t++){
    rc = pthread_create(&threads[t], NULL, coder, NULL);
    if (rc){
      printf("ERROR; return code from pthread_create() is %d\n", rc);
      exit(-1);
    }
  }
 
  sleep(5);
 
  for(t=1;t<=4;t++){
    pthread_mutex_lock(&g_task_lock);
    tail+=t;
    printf("I am Boss, I assigned %d tasks, I notify all coders!\n", t);
    pthread_cond_broadcast(&g_task_cv);
    pthread_mutex_unlock(&g_task_lock);
    sleep(20);
  }
 
  pthread_mutex_lock(&g_task_lock);
  quit = 1;
  pthread_cond_broadcast(&g_task_cv);
  pthread_mutex_unlock(&g_task_lock);
 
  pthread_mutex_destroy(&g_task_lock);
  pthread_cond_destroy(&g_task_cv);
  pthread_exit(NULL);
}

首先,我们创建了 10 个任务,每个任务一个字符,放在一个数组里面,另外有两个变量 head 和 tail,表示当前分配的工作从哪里开始,到哪里结束。如果 head 等于 tail,则当前的工作分配完毕;如果 tail 加 N,就是新分配了 N 个工作。

接下来声明的 pthread_mutex_t g_task_lock 和 pthread_cond_t g_task_cv,是用于通知和抢任务的,工作模式如下图所示:

图中左边的就是员工的工作模式,对于每一个员工 coder,先要获取锁 pthread_mutex_lock,这样才能保证一个任务只分配给一个员工。

然后,我们要判断有没有任务,也就是说,head 和 tail 是否相等。如果不相等的话,就是有任务,则取出 head 位置代表的任务 task,然后将 head 加一,这样整个任务就给了这个员工,下个员工来抢活的时候,也需要获取锁,获取之后抢到的就是下一个任务了。当这个员工抢到任务后,pthread_mutex_unlock 解锁,让其他员工可以进来抢任务。抢到任务后就开始干活了,这里没有真正开始干活,而是 sleep,也就是摸鱼了 5 秒。

如果发现 head 和 tail 相当,也就是没有任务,则需要调用 pthread_cond_wait 进行等待,这个函数会把锁也作为变量传进去。这是因为等待的过程中需要解锁,要不然,你不干活,等待睡大觉,还把门给锁了,别人也干不了活,而且老板也没办法获取锁来分配任务。

一开始三个员工都是在等待的状态,因为初始化的时候,head 和 tail 相等都为零。

现在我们把目光聚焦到老板这里,也就是主线程上。它初始化了条件变量和锁,然后创建三个线程,也就是我们说的招聘了三个员工。

接下来要开始分配任务了,总共 10 个任务。老板分四批分配,第一批一个任务三个人抢,第二批两个任务,第三批三个任务,正好每人抢到一个,第四批四个任务,可能有一个员工抢到两个任务。这样三个员工,四批工作,经典的场景差不多都覆盖到了。

老板分配工作的时候,也是要先获取锁 pthread_mutex_lock,然后通过 tail 加一来分配任务,这个时候 head 和 tail 已经不一样了,但是这个时候三个员工还在 pthread_cond_wait 那里睡着呢,接下来老板要调用 pthread_cond_broadcast 通知所有的员工,来活了,醒醒,起来干活。

这个时候三个员工醒来后,先抢锁,生怕老板只分配了一个任务,让别人抢去。当然抢锁这个动作是 pthread_cond_wait 在收到通知的时候,自动做的,不需要我们另外写代码。

抢到锁的员工就通过 while 再次判断 head 和 tail 是否相同。这次因为有了任务,不相同了,所以就抢到了任务。而没有抢到任务的员工,由于抢锁失败,只好等待抢到任务的员工释放锁,抢到任务的员工在 tasklist 里面拿到任务后,将 head 加一,然后就释放锁。这个时候,另外两个员工才能从 pthread_cond_wait 中返回,然后也会再次通过 while 判断 head 和 tail 是否相同。不过已经晚了,任务都让人家抢走了,head 和 tail 又一样了,所以只好再次进入 pthread_cond_wait,接着等任务。这里,我们只解析了第一批一个任务的工作的过程。如果运行上面的程序,可以得到下面的结果。我将整个过程在里面写了注释,你看起来就比较容易理解了。

root@deployer createthread]# ./a.out
// 招聘三个员工,一开始没有任务,大家睡大觉
No task now! Thread 3491833600 is waiting!
No task now! Thread 3483440896 is waiting!
No task now! Thread 3475048192 is waiting!
// 老板开始分配任务了,第一批任务就一个,告诉三个员工醒来抢任务
I am Boss, I assigned 1 tasks, I notify all coders!
// 员工一先发现有任务了,开始抢任务
Have task now! Thread 3491833600 is grabing the task !
// 员工一抢到了任务 A,开始干活
Thread 3491833600 has a task A now! 
// 员工二也发现有任务了,开始抢任务,不好意思,就一个任务,让人家抢走了,接着等吧
Have task now! Thread 3483440896 is grabing the task !
No task now! Thread 3483440896 is waiting!
// 员工三也发现有任务了,开始抢任务,你比员工二还慢,接着等吧
Have task now! Thread 3475048192 is grabing the task !
No task now! Thread 3475048192 is waiting!
// 员工一把任务做完了,又没有任务了,接着等待
Thread 3491833600 finish the task A !
No task now! Thread 3491833600 is waiting!
// 老板又有新任务了,这次是两个任务,叫醒他们
I am Boss, I assigned 2 tasks, I notify all coders!
// 这次员工二比较积极,先开始抢,并且抢到了任务 B
Have task now! Thread 3483440896 is grabing the task !
Thread 3483440896 has a task B now! 
// 这次员工三也聪明了,赶紧抢,要不然没有年终奖了,终于抢到了任务 C
Have task now! Thread 3475048192 is grabing the task !
Thread 3475048192 has a task C now! 
// 员工一上次抢到了,这次抢的慢了,没有抢到,是不是飘了
Have task now! Thread 3491833600 is grabing the task !
No task now! Thread 3491833600 is waiting!
// 员工二做完了任务 B,没有任务了,接着等待
Thread 3483440896 finish the task B !
No task now! Thread 3483440896 is waiting!
// 员工三做完了任务 C,没有任务了,接着等待
Thread 3475048192 finish the task C !
No task now! Thread 3475048192 is waiting!
// 又来任务了,这次是三个任务,人人有份
I am Boss, I assigned 3 tasks, I notify all coders!
// 员工一抢到了任务 D,员工二抢到了任务 E,员工三抢到了任务 F
Have task now! Thread 3491833600 is grabing the task !
Thread 3491833600 has a task D now! 
Have task now! Thread 3483440896 is grabing the task !
Thread 3483440896 has a task E now! 
Have task now! Thread 3475048192 is grabing the task !
Thread 3475048192 has a task F now! 
// 三个员工都完成了,然后都又开始等待
Thread 3491833600 finish the task D !
Thread 3483440896 finish the task E !
Thread 3475048192 finish the task F !
No task now! Thread 3491833600 is waiting!
No task now! Thread 3483440896 is waiting!
No task now! Thread 3475048192 is waiting!
// 公司活越来越多了,来了四个任务,赶紧干呀
I am Boss, I assigned 4 tasks, I notify all coders!
// 员工一抢到了任务 G,员工二抢到了任务 H,员工三抢到了任务 I
Have task now! Thread 3491833600 is grabing the task !
Thread 3491833600 has a task G now! 
Have task now! Thread 3483440896 is grabing the task !
Thread 3483440896 has a task H now! 
Have task now! Thread 3475048192 is grabing the task !
Thread 3475048192 has a task I now! 
// 员工一和员工三先做完了,发现还有一个任务开始抢
Thread 3491833600 finish the task G !
Thread 3475048192 finish the task I !
// 员工三没抢到,接着等
No task now! Thread 3475048192 is waiting!
// 员工一抢到了任务 J,多做了一个任务
Thread 3491833600 has a task J now! 
// 员工二这才把任务 H 做完,黄花菜都凉了,接着等待吧
Thread 3483440896 finish the task H !
No task now! Thread 3483440896 is waiting!
// 员工一做完了任务 J,接着等待
Thread 3491833600 finish the task J !
No task now! Thread 3491833600 is waiting!

总结时刻

这一节,我们讲了如何创建线程,线程都有哪些数据,如何对线程数据进行保护。

写多线程的程序是有套路的,我这里用一张图进行总结。你需要记住的是,创建线程的套路、mutex 使用的套路、条件变量使用的套路。

课堂练习

这一节讲了多线程编程的套路,但是我没有对于每一个函数进行详细的讲解,相关的还有很多其他的函数可以调用,这需要你自己去学习。这里我给你推荐一本书 ProgrammingwithPOSIXThreadsProgrammingwithPOSIXThreads,你可以系统地学习一下。另外,上面的代码,建议你一定要上手编译运行一下。


线程栈上的本地数据和线程私有数据有什么本质区别,能否举例说明?谢谢

作者回复: 相当于全局变量,可以在多个函数间共享,比如线程里面有多个函数

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

Linux 线程创建 的相关文章

  • Linux C++ 错误:未定义对“dlopen”的引用

    我在 Linux 上使用 C Eclipse 工作 并且想要使用一个库 Eclipse 向我显示一个错误 undefined reference to dlopen 你知道解决办法吗 这是我的代码 include
  • 无法在 Linux 的 NetBeans 中编译 C++ 和 OpenGL (GLFW) 的简单源代码

    我开始学习 OpenGL glfw 我从教程中复制源代码并尝试编译它 但出现了错误 我想我已经正确安装了所有头文件 glm glfw 等 这是我的来源 我没有在头文件中使用这些字符 include iostream include stdi
  • Pthreads - 高内存使用率

    我正在用 C 编写一些东西 在 256Mb 系统上的 Linux 中创建大量 Pthread 我通常有 200Mb 的免费空间 当我使用少量线程运行该程序时 它可以工作 但是一旦我让它创建大约 100 个线程 它就会出现错误 因为系统内存不
  • 何时用引号将 shell 变量括起来?

    我应该或不应该在 shell 脚本中用引号括住变量吗 例如 下列说法正确的是 xdg open URL eq 2 or xdg open URL eq 2 如果是这样 为什么 一般规则 如果它可以为空或包含空格 或实际上任何空格 或特殊字符
  • 如何在Linux上用C/C++编写Web服务器[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我正在考虑在 Linux 平台上开发一个小型 阅读 初级 Web 服务器 但我不知道从哪里开始 我希望它能够做的是 监听特定端口 接受
  • Linux 内核使用的设备树文件 (dtb) 可视化工具? [关闭]

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

    我最近发现了 Scapy 它看起来很棒 我正在尝试查看 NIC 上物理环回模块 存根上的简单流量 但是 Scapy sniff 没有给出任何结果 我正在做的发送数据包是 payload data 10 snf sniff filter ic
  • 在 Linux 中重新启动时,新创建的文件变为 0 kb(数据被覆盖为空)

    我遇到了一个奇怪的问题 这让我发疯 当前的任务是在 root 用户第一次登录时启动一组文件 并在同一用户第二次登录时启动另一组文件 我决定使用 profile 和 bashrc 文件 并在第一次登录期间发生的任务结束时重新加载 bashrc
  • 如何从 Bash 命令行在后台 Vim 打开另一个文件?

    我正在从使用 Gvim 过渡到使用控制台 Vim 我在 Vim 中打开一个文件 然后暂停 Vim 在命令行上运行一些命令 然后想返回到 Vim Ctrl Z 在正常模式下 暂停 Vim 并返回到控制台 fg可用于将焦点返回到 Vim job
  • Docker忽略limits.conf(试图解决“打开文件太多”错误)

    我正在运行一个 Web 服务器 该服务器正在处理数千个并发 Web 套接字连接 为了实现这一点 在 Debian linux 我的基本镜像是 google debian wheezy 在 GCE 上运行 上 打开文件的默认数量设置为 100
  • bash while 循环的布尔表达式中的 -lt 意味着什么?

    我猜测它代表小于基于输出 但是我在哪里可以找到有关此语法的文档 bin bash COUNTER 0 while COUNTER lt 10 do echo The counter is COUNTER let COUNTER COUNTE
  • Linux shell 从用户输入中获取设备 ID

    我正在为一个程序编写安装脚本 该程序需要在其配置中使用 lsusb 的设备 ID 因此我正在考虑执行以下操作 usblist lsusb put the list into a array for each line use the arr
  • 如何并行执行4个shell脚本,我不能使用GNU并行?

    我有4个shell脚本dog sh bird sh cow sh和fox sh 每个文件使用 xargs 并行执行 4 个 wget 来派生一个单独的进程 现在我希望这些脚本本身能够并行执行 由于某些我不知道的可移植性原因 我无法使用 GN
  • LINUX:如何锁定内存中进程的页面

    我有一个 LINUX 服务器 运行一个具有大量内存占用的进程 某种数据库引擎 该进程分配的内存太大 需要将其中一部分换出 换出 我想做的是将所有其他进程 或正在运行的进程的子集 的内存页面锁定在内存中 以便只有数据库进程的页面被换出 例如
  • 如何在不使用 IDE 的情况下在 Linux 上运行 Java 项目

    我是 Java 新手 基本上 我开发了一个java项目 其中包含Eclipse中的多个Java包 该项目在我安装了 redhat Linux 的桌面上运行正常 然而 我需要在一个更强大的没有安装X11的Linux服务器 redhat ent
  • 在 docker 中重定向命令输出

    我想为我的服务器做一些简单的日志记录 它是一个在 Docker 容器中运行的小型 Flask 应用程序 这是 Dockerfile Dockerfile FROM dreen flask MAINTAINER dreen WORKDIR s
  • vmsplice() 和 TCP

    在原来的vmsplice 执行 有人建议 http lwn net Articles 181169 如果您的用户态缓冲区是管道中可容纳的最大页面数的 2 倍 则缓冲区后半部分成功的 vmsplice 将保证内核使用缓冲区的前半部分完成 但事
  • 批量删除文件名中包含 BASH 中特殊字符的子字符串

    我的目录中有一个文件列表 opencv calib3d so2410 so opencv contrib so2410 so opencv core so2410 so opencv features2d so2410 so opencv
  • 在内核代码中查找函数的最佳方法[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我开始浏览内核代码 遇到的一件事是如何跟踪函数调用 结构定义等 有没有一种好的方法可以快速跳转到函数定义并退出 我尝试过 Source N
  • SSH,运行进程然后忽略输出

    我有一个命令可以使用 SSH 并在 SSH 后运行脚本 该脚本运行一个二进制文件 脚本完成后 我可以输入任意键 本地终端将恢复到正常状态 但是 由于该进程仍在我通过 SSH 连接的计算机中运行 因此任何时候它都会登录到stdout我在本地终

随机推荐

  • 如何将文档上传到 ChatGPT

    OpenAI 一直在为 ChatGPT 添加几个有趣的功能 包括对网页浏览和插件的支持 但是 仍然没有办法本地上传文档并根据其上下文提出问题 当然 有些用户可以在他们的数据上训练 AI 聊天机器人 但并不是每个人都了解如何设置工具和库 因此
  • 华为OD机试 C++ 去除多余空格

    题目 你需要写一个功能 它能处理一段文本 去除其中不必要的空格 但是如果这些空格被一对单引号包围起来 就保留它们不变 同时 你还要调整一些特定词汇的位置 这些词汇的位置会以坐标的方式给出 坐标要基于新的文本 特别注意 关键词的位置一定不是空
  • Unity 入门 Input 类

    1 获得键盘 Input GetKey KeyCode A Input GetKeyDown KeyCode A Input GetKeyUp KeyCode A 2 获得鼠标信息 Input mousePosition 鼠标位置 Inpu
  • 关系型数据库是如何运作的

    一说到关系型数据库 我总感觉缺了点什么 如果你尝试透过 关系型数据库是如何运作的 的关键词句来进行搜索 其搜索结果是少量的而且内容是简短的 难道说是由于它已经太老旧而已经不再流行吗 作为一名开发者 我讨厌使用我不明白的技术 此外 关系型数据
  • s、x、t -learner

  • DICOM之Transfer Syntax

    Transfer Syntax A Transfer Syntax is a set of encoding rules able to unambiguously represent one or more Abstract Syntax
  • ChatGPT在线个人小助手应用搭建

    ChatGPT在线个人小助手应用搭建 在线体验 点我在线体验 因为openAI账户申请后会默认有18美元的账户 openAI每次调用大概会花掉0 01美元 所以为了防止恶意刷api 无意义聊天 页面做了密码限制 如果密码不对 是不会启用op
  • mysql存储引擎层和服务器层,MySQL底层架构原理,工作流程和存储引擎的数据结构讲解...

    数据库 DataBase 是存放用户数据的地方 当用户访问 操作数据库中的数据时 需要数据库管理系统的帮助 数据管理系统的全称是DataBase Management System 简称DBMS 通常情况下我们会把数据库和数据库管理系统笼统
  • 网页端无法复制粘贴的解决方案

    由于瑞格系统无法复制粘贴 写java代码比较难受 所以就找了一些方法来解决网页端无法复制粘贴的问题 1 打开浏览器的设置界面 并打开拓展程序 2 在拓展程序中选择左上角的拓展程序 并打开Chrome网上应用商店 3 在Chrome网上应用商
  • 多线程JUC并发篇常见面试详解

    文章目录 1 JUC 简介 2 线程和进程 3 并非与并行 4 线程的状态 5 wait sleep的区别 6 Lock 锁 重点 1 Lock锁 2 公平非公平 3 ReentrantLock 构造器 4 Lock 锁实现步骤 7 syn
  • 百炼成钢;JavaScript逆向九大专题详解

    JavaScript是一种脚本语言 通常用于在Web浏览器中编写交互式前端应用程序 它是一种解释性语言 可以在客户端 浏览器 和服务器端 Node js 上运行 JavaScript可以用于创建动态网页 Web应用程序 游戏 移动应用程序等
  • unity 获取鼠标键盘

    unity 获取鼠标键盘 在做项目中我们经常会用到鼠标键盘 那么怎么去获取鼠标键盘呢 接下里我带大家了解一下 首先是获取鼠标 大家记住无论是获取鼠标还是获取键盘都要用到unity中的一个小小的组件首先在unity上方的选项卡中选择edit
  • RocketMQ(三) broker启动

    RocketMQ源码版本V5 0 0 可兼容之前的版本 因为整理资料的时候 之前的版本 和V5版本有所出入 核心流程基本还是大同小异的 此前已经总结了NameServer的启动流程源码 现在来了解Broker的启动流程 在RocketMQ启
  • 第一章 基础算法(一)ACwing 快速,归并,二分

    第一章 基础算法 一 一 内容概述 主要思想掌握 深刻的理解 代码模板理解以及背过 掌握思想 模板题目练习 理解 记忆 1 排序 快排 归并排序 2 二分 整数二分 浮点数二分 二 快速排序 快速排序的主要思想是基于分治的 第一步就是是确定
  • gd32F450单片机 ADC+DMA

    接触国产单片机不久 好多配置的东西记不住 写下来分享然后也方便自己以后拿来看看 欢迎大家把踩坑的部分分享一下 本次是ADC配置和DMA采集的配置部分 某些参数错误会导致内存溢出 影响到其他变量或者参数表的值 引脚为PB0和PB1两个 一 相
  • 三款强大的 AI 编程工具,可以轻松替换 Github Copilot

    大家好 提起Github Copilot 相信很多读者朋友们都听说过甚至使用过 作为Github研发的一款先进的编程辅助插件 它可以在我们日常编写代码的过程中 根据代码的上下文内容 注释等信息自动推断生成高质量的代码 很大程度上提升我们的代
  • Linux中一个网络包的发送/接收流程

    如果你对Linux是如何实现 对用户原始的网络包进行协议头封装与解析 为什么会粘包拆包 期间网络包经历了哪些缓冲区 经历了几次拷贝 CPU DMA TCP又是如何实现滑动 拥塞窗口 这几个话题感兴趣的话 不妨看下去吧 1 Linux发送HT
  • linux系统下重启网络服务的两种方法

    linux系统下重启网络服务的两种方法 发布时间 2020 04 02 11 25 25 来源 亿速云 阅读 207 作者 小新 今天小编给大家分享的是linux系统下重启网络服务的两种方法 很多人都不太了解 今天小编为了让大家更加了解li
  • 【android系统】android系统升级流程分析(二)---update升级包分析

    接下来我们将通过几篇文章来分析update zip包在具体Android系统升级的过程 来理解Android系统中Recovery模式服务的工作原理 今天让我先来分析下升级包update zip 一 目录结构 update zip包的目录结
  • Linux 线程创建

    如何创建线程 看来多线程还是有很多好处的 接下来我们来看一下 如何使用线程来干一件大事 假如说 现在我们有 N 个非常大的视频需要下载 一个个下载需要的时间太长了 按照刚才的思路 我们可以拆分成 N 个任务 分给 N 个线程各自去下载 我们