为什么多个 pthread_create() 调用同一个函数会导致分段错误?

Why multiple pthread_create() calling same function end up in segmentation fault?

我正在尝试 运行 一段具有多个线程的代码。我的理解是,即使线程共享进程内存,从线程调用的每个函数都有自己的堆栈框架,即寄存器。因此,从多个线程调用同一个函数应该没有问题(如果我错了请指正)。

我有以下代码。此代码通常与单个线程一起工作。我的目标是 运行 同时执行此代码的某些部分。

#include "rsa/config.h"
#include "rsa/aes.h"
#include "rsa/bignum.h"
#include "rsa/rsa.h"
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <pthread.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


#define NUM_OF_THREAD 8  

void thread_function(void * input){
    printf("Test thread\n");
    unsigned char private_encrypt[KEY_BUFFER_SIZE];
    int total_dec=1000;
    unsigned char * buffer = 0;
    long length;
    unsigned char msg_decrypted[KEY_LEN];


    FILE * fp2 = fopen ("msg.enc", "rb");
    int size1=KEY_BUFFER_SIZE;
    if(fp2){
        while(size1>0){
            fread(private_encrypt,1,sizeof (private_encrypt),fp2);
            size1=size1-1;
        }
    }
    fclose(fp2);

    FILE * fp = fopen ("rsa_priv.txt", "rb");

    if (fp){
        fseek (fp, 0, SEEK_END);
        length = ftell (fp);
        fseek (fp, 0, SEEK_SET);
        buffer = calloc (1,length+1);
        if (buffer){
            fread (buffer, 1, length, fp);
        }
        fclose (fp);
    }

    // initialize rsaContext
    rsa_context rsaContext;
    rsa_init(&rsaContext,RSA_PKCS_V15, 0);
    rsaContext.len=KEY_LEN;

    // spliting keys and load into rsa context
    const char s[3] = "= ";
    char *token;
    int k=0, size;

    // get the first token
    token = strtok(buffer, s);

    // walk through other tokens
    while( token != NULL ) {
        size = strlen(token);

        switch (k) {
            case 1:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.N, 16, token);
                break;

            case 3:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.E, 16, token);
                break;

            case 5:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.D, 16, token);
                break;

            case 7:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.P, 16, token);
                break;

            case 9:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.Q, 16, token);
                break;

            case 11:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.DP, 16, token);
                break;

            case 13:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.DQ, 16, token);
                break;

            case 15:
                token[size-1]='[=11=]';
                mpi_read_string(&rsaContext.QP, 16, token);
                break;
        }
        k=k+1;
        token = strtok(NULL, "= \n");
    }
    printf("Here\n");

    while (total_dec>=0) {
        if( rsa_private(&rsaContext,private_encrypt, msg_decrypted) != 0 ) {
            printf( "Decryption failed! %d\n", rsa_private(&rsaContext,private_encrypt, msg_decrypted));
        }else{
            printf("%d Decrypted plaintext-----> %s\n",total_dec, msg_decrypted );
        }
        total_dec--;
    }
    
}


int main(){ 
    int i;
    clock_t t;

    // total number of thread
    pthread_t ths[NUM_OF_THREAD];

    // start time count
    t = clock();

    for (i = 0; i < NUM_OF_THREAD; i++) {
        pthread_create(&ths[i], NULL, thread_function, NULL);
    }

    for (i = 0; i < NUM_OF_THREAD; i++) {
        void* res;
        pthread_join(ths[i], &res);
    }

    // end time count
    t = clock() - t;
    double time_taken = ((double)t)/CLOCKS_PER_SEC; // in seconds
    printf("Took %f seconds to execute \n", time_taken);
    printf("Total Cycle %f  \n", t);
    return 0;
}

当我 运行 使用单线程的代码时,代码运行良好并产生了理想的结果。如果我将 NUM_OF_THREAD 设置为不同的值,我的程序就会开始表现得很奇怪。有时所有线程都已创建并完美运行,有时线程已创建但之后出现分段错误。有时几个线程工作,几个线程不工作。

错误类型:1(设置NUM_OF_THREAD 8),输出:

因此,我尝试 运行 具有 8 个线程的代码。不同的尝试给我不同的结果

$./multiThread 
Test thread
Test thread
Test thread
Test thread
Test thread
Test thread
Test thread
Here
Here
Here
Here
Segmentation fault (core dumped)

错误类型:2(设置NUM_OF_THREAD 8),输出:

$./multiThread 
Test thread
Test thread
Test thread
Test thread
Test thread
Here
Here
Test thread
Here
Here
Here
Decryption failed! -17156
Decryption failed! -17156
Segmentation fault (core dumped)

错误类型:3(设置NUM_OF_THREAD 8),输出:

在这个试验中,有几个线程失败了,但其中一些线程产生了预期的结果。

$./multiThread
 Decryption failed! -17156
 Decryption failed! -17156
 .
 .
 .
 .
3 Decrypted plaintext-----> xxxxxxxxxxx

2 Decrypted plaintext-----> xxxxxxxxxxx

2 Decrypted plaintext-----> xxxxxxxxxxx

1 Decrypted plaintext-----> xxxxxxxxxxx

1 Decrypted plaintext-----> xxxxxxxxxxx

0 Decrypted plaintext-----> xxxxxxxxxxx

0 Decrypted plaintext-----> xxxxxxxxxxx

Took 174.413690 seconds to execute 
Total Cycle 174.413690  

我的预感,问题出在rsa_private()。这是在 #include "rsa/rsa.h" 中声明的。这被认为是共享资源吗?如果我不正确,有人可以告诉我我在这里做错了什么吗?我该怎么做才能解决这个问题?我不想使用锁,因为它会导致我的代码变慢。

我的 CPU:Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz,四核,每核 2 个线程。

strtok() 不是线程安全的,因为它在内部使用静态缓冲区。请改用 strtok_r()

来自 the man page

   ┌────────────────────────┬───────────────┬───────────────────────┐
   │Interface               │ Attribute     │ Value                 │
   ├────────────────────────┼───────────────┼───────────────────────┤
   │strtok()                │ Thread safety │ MT-Unsafe race:strtok │
   ├────────────────────────┼───────────────┼───────────────────────┤
   │strtok_r()              │ Thread safety │ MT-Safe               │
   └────────────────────────┴───────────────┴───────────────────────┘

您的理论是正确的,但是 strtok() 不符合您的假设,strtok_r() 是这样做的函数。

strtok() 函数不是 MT-safe,这意味着您不应该在多线程环境中使用它。 strtok 的可重入(因此 MT-safe)版本是 strtok_r,这就是您应该使用的版本。

strtok_rhere.

手册

strtok 使用 static(永远不会是 MT-safe)存储其状态并更新该状态不同步。这使得它在多线程应用程序中使用时严重暴露于竞争条件,如果这样做会导致不可预测的行为。

strtok_r 在本地保存它的状态(因此额外的 saveptr 参数),使其可重入并且 MT-safe 隐式。

如果您想亲眼看看,请检查 strtok 是如何实现的 here:

char *
strtok (char *s, const char *delim)
{
  static char *olds;
  return __strtok_r (s, delim, &olds);
}