【问题标题】:Passing data and retrieving data via same struct using threads使用线程通过相同的结构传递数据和检索数据
【发布时间】:2015-08-16 12:16:47
【问题描述】:

我需要从一个单独的线程中读取一个文件,以避免我的 opengl 程序的流程出现问题。我已经这样做了,用于加载纹理并使用全局变量混合它们,效果很好。

但是现在我需要一些单独的线程来读取小数据文件。

我创建了一个struct,它基本上包含2个args和1个结果。

看来我做不到,或者我在某个地方(或很多地方)弄错了

这是我的非概念证明的示例代码:

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

#define content_file "/home/tias/content.txt" //this file contains "foobar!!"

typedef struct {
    int* reading;     // 0 = not reading , 1 = reading , 2 = finished reading
    char* content;    // content of the file
    char* file;       // file to read
} struct_file_content;

struct_file_content first_file;

void *thread_read_file ( void* data ) {

    struct_file_content *thisdata = (struct_file_content*) data;
    long length;
  int i = 0;
  char readchar;
  char * thiscontent = 0;
    int reading_finished = 2;
    int *ptr_to_reading_finished = (int*)malloc(sizeof(int));

    ptr_to_reading_finished = &reading_finished;

    fprintf(stdout,"thread_Read_file called with file: %s and reading: %u\n",(*thisdata).file,*(*thisdata).reading);

    FILE * f = fopen ((*thisdata).file, "r");

    if (f) {
    fseek (f, 0, SEEK_END);
    length = ftell (f);
    fseek (f, 0, SEEK_SET);
    if (length > 39) {
      fprintf(stderr,"file %s is too big\n",(*thisdata).file);
      exit (1);
    }
        thiscontent = (char*) malloc (length*sizeof(char));
        if ( thiscontent ) {
            fread (thiscontent, 1, length, f);
        }
    fclose (f);
        thisdata->content = thiscontent;
  } else {
    fprintf (stderr, "cannot read file %s\n",(*thisdata).file);
    exit (1);
  }

    sleep(1);

    thisdata->reading = ptr_to_reading_finished;

    fprintf(stdout,"finished reading: %u\n",*(*thisdata).reading);
    fprintf(stdout,"content: %s\n",thiscontent);

  pthread_exit(NULL);
  return NULL;
}

main()
{
    pthread_t thread1;
    int filename_length = strlen(content_file);
    int rfinish = 2;
    int rbegin  = 1;

    first_file.reading = (int*)  malloc(sizeof(int));
    first_file.file    = (char*) malloc((filename_length+1)*sizeof(char));
    first_file.reading = &rbegin;
    strcpy(first_file.file,content_file);

  pthread_create( &thread1, NULL, thread_read_file, (void*) &first_file);

    while ( *(first_file.reading) != 2 ) {  
        fprintf(stdout,"still reading, reading: %u\n",*(first_file.reading));
        sleep(1);
    }

    fprintf(stdout,"exited control loop with file: %s, reading: %u, content: %s\n",first_file.file, *(first_file.reading), first_file.content);
}

结果如下:

~/repository/thread/test$ ./tt
still reading, reading: 1
thread_Read_file called with file: /home/tias/content.txt and reading: 1
still reading, reading: 1
still reading, reading: 1
still reading, reading: 1
finished reading: 2
content: foobar!!

still reading, reading: 0
still reading, reading: 0

我期待 reading = 2 退出循环,而不是 0。

知道我必须修改什么才能使其正常工作吗?

我读过关于互斥锁的文章,可能是这样吗?

我发现我的解决方案很优雅但不起作用,非常感谢您的帮助。

【问题讨论】:

  • 使用%d 打印int,而不是%u。另外,将first_file.reading = (int*) malloc(sizeof(int)); 更改为first_file.reading = calloc(1, sizeof(int));
  • 我这样做了,同样的事情发生了。
  • 这些都不是原子的,也没有一个受互斥锁保护。这段代码是活泼的,因此是未定义的行为。

标签: c multithreading struct


【解决方案1】:
typedef struct {
    int* reading;     // 0 = not reading , 1 = reading , 2 = finished reading
    char* content;    // content of the file
    char* file;       // file to read
} struct_file_content;

为什么reading 是指针类型?它在代码中用作标志,绝对没有必要将其设为指针,尤其是考虑到您为此结构字段动态分配内存。它使设计复杂化并且是不必要的。继续,将其设为int,而不是指针:

typedef struct {
    int reading;     // 0 = not reading , 1 = reading , 2 = finished reading
    char *content;    // content of the file
    char *file;       // file to read
} struct_file_content;

这简化了main()中用于设置first_file的代码:

first_file.reading = 1;
first_file.content = NULL;
first_file.file = content_file;

请注意,不需要动态分配内存first_file.file,因为您在编译时就知道文件名(和大小)。保持简单。

接下来,忽略来自pthread_create(3) 的可能错误返回值。如果失败,它会返回非零值,您应该检查一下。这样的事情会做:

pthread_t thread1;
int thread_res = pthread_create(&thread1, NULL, thread_read_file, &first_file);
if (thread_res != 0) {
    fprintf(stderr, "pthread_create(3) error: %s\n", strerror(thread_res));
    exit(EXIT_FAILURE);
}

等待读取完成的代码是错误的和活泼的,您需要使用互斥锁同步访问struct_file_contentreading 字段,或者在访问first_file 之前正确等待线程终止再次。由于代码除了等待线程什么都不做,pthread_join(3) 在这里是一个更合理的选择。你会做这样的事情:

int join_res = pthread_join(thread1, NULL);
if (join_res != 0) {
    fprintf(stderr, "pthread_join(3) error: %s\n", strerror(join_res));
    exit(EXIT_FAILURE);
}

thread_read_file() 中,您可能在处理错误时需要pthread_exit(3) 而不是exit(2),因为后者将终止整个进程,而不仅仅是本地线程。您还需要处理malloc(3) 错误。

以下是解决所有这些问题的代码:

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

#define content_file "/home/tias/content.txt" //this file contains "foobar!!"

typedef struct {
    int reading;     // 0 = not reading , 1 = reading , 2 = finished reading
    char *content;   // content of the file
    char *file;      // file to read
} struct_file_content;

struct_file_content first_file;

void *thread_read_file(void *data) {
    struct_file_content *thisdata = data;

    long length;
    char *thiscontent = NULL;

    fprintf(stdout,"thread_Read_file called with file: %s and reading: %u\n", thisdata->file, thisdata->reading);

    FILE *f = fopen (thisdata->file, "r");

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek (f, 0, SEEK_SET);
        if (length > 39) {
            fprintf(stderr,"file %s is too big\n", thisdata->file);
            pthread_exit(NULL);
        }

        thiscontent = malloc(length);

        if (thiscontent) {
            fread(thiscontent, 1, length, f);
        } else {
            perror("malloc(3) error");
            pthread_exit(NULL);
        }

        fclose(f);
        thisdata->content = thiscontent;
    } else {
        fprintf(stderr, "cannot open file %s\n", thisdata->file);
        pthread_exit(NULL);
    }

    thisdata->reading = 2;

    fprintf(stdout, "finished reading: %u\n", thisdata->reading);
    fprintf(stdout, "content: %s\n", thiscontent);

    return NULL;
}

int main(void) {
    first_file.reading = 1;
    first_file.content = NULL;
    first_file.file = content_file;

    pthread_t thread1;
    int thread_res = pthread_create(&thread1, NULL, thread_read_file, &first_file);
    if (thread_res != 0) {
        fprintf(stderr, "pthread_create(3) error: %s\n", strerror(thread_res));
        exit(EXIT_FAILURE);
    }

    int join_res = pthread_join(thread1, NULL);
    if (join_res != 0) {
        fprintf(stderr, "pthread_join(3) error: %s\n", strerror(join_res));
        exit(EXIT_FAILURE);
    }

    fprintf(stdout, "Read file %s, reading: %u, content: %s\n", first_file.file, first_file.reading, first_file.content);

    return 0;
}

更新

从 cmets 看来,您似乎想要进行一些额外的处理并定期测试(在您方便时)线程是否已完成读取。您使用标志来测试终止的方法大部分是正确的,但是您应该同步对struct_file_contentreading 字段的访问,以确保您始终获得一致的值。因此,我建议向struct_file_content 添加一个互斥体,用于控制对reading 字段的并发访问。每次需要读取或更新 reading 时都应锁定互斥锁。

所以,结构定义变成:

typedef struct {
    pthread_mutex_t read_mutex; // synchronize access to reading flag
    int reading;     // 0 = not reading , 1 = reading , 2 = finished reading
    char *content;   // content of the file
    char *file;      // file to read
} struct_file_content;

然后,作为初始化struct_file_content 的一部分,您需要记住初始化互斥锁。以下是您在 main() 中的做法:

int mutex_err = pthread_mutex_init(&first_file.read_mutex, NULL);

if (mutex_err != 0) {
    fprintf(stderr, "pthread_mutex_init(3) error: %s\n", strerror(mutex_err));
    exit(EXIT_FAILURE);
}

first_file.reading = 1;
first_file.content = NULL;
first_file.file = content_file;

现在,main() 中的循环只是锁定互斥体,检查reading 字段的状态(如果等于 2,则中断),然后解锁互斥体。比如:

int read_done = 0;
while (!read_done) {

    mutex_err = pthread_mutex_lock(&first_file.read_mutex);
    if (mutex_err != 0) {
        fprintf(stderr, "pthread_mutex_lock(3) error: %s\n", strerror(mutex_err));
        exit(EXIT_FAILURE);
    }

    /* Reading is finished when first_file.reading == 2 */
    read_done = (first_file.reading == 2);

    if (first_file.reading != 2)
        printf("Still reading, reading: %u\n", first_file.reading);

    mutex_err = pthread_mutex_unlock(&first_file.read_mutex);
    if (mutex_err != 0) {
        fprintf(stderr, "pthread_mutex_unlock(3) error: %s\n", strerror(mutex_err));
    }
}

当然,在修改reading之前,你还需要更新线程函数来锁定互斥锁:

void *thread_read_file(void *data) {

    struct_file_content *thisdata = data;
    int mutex_res;
    long length;
    char *thiscontent = NULL;

    mutex_res = pthread_mutex_lock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to acquire mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    fprintf(stdout, "thread_read_file() called with file: %s and reading: %u\n", thisdata->file, thisdata->reading);

    mutex_res = pthread_mutex_unlock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to release mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    FILE *f = fopen(thisdata->file, "r");

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek (f, 0, SEEK_SET);

        if (length > 39) {
            fprintf(stderr, "file %s is too big\n", thisdata->file);
            pthread_exit(NULL);
        }

        thiscontent = malloc(length);

        if (thiscontent) {
            fread(thiscontent, 1, length, f);
        } else {
            perror("malloc(3) error");
            pthread_exit(NULL);
        }

        fclose(f);
        thisdata->content = thiscontent;

    } else {
        fprintf(stderr, "cannot open file %s\n", thisdata->file);
        pthread_exit(NULL);
    }

    mutex_res = pthread_mutex_lock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to acquire mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    thisdata->reading = 2;
    fprintf(stdout, "finished reading: %u\n", thisdata->reading);

    mutex_res = pthread_mutex_unlock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to release mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    fprintf(stdout, "content: %s\n", thiscontent);

    return NULL;
}

应该够了。完整代码如下:

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

#define content_file "/home/tias/content.txt" //this file contains "foobar!!"

typedef struct {
    pthread_mutex_t read_mutex; // synchronize access to reading flag
    int reading;     // 0 = not reading , 1 = reading , 2 = finished reading
    char *content;   // content of the file
    char *file;      // file to read
} struct_file_content;

struct_file_content first_file;

void *thread_read_file(void *data) {

    struct_file_content *thisdata = data;
    int mutex_res;
    long length;
    char *thiscontent = NULL;

    mutex_res = pthread_mutex_lock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to acquire mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    fprintf(stdout, "thread_read_file() called with file: %s and reading: %u\n", thisdata->file, thisdata->reading);

    mutex_res = pthread_mutex_unlock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to release mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    FILE *f = fopen(thisdata->file, "r");

    if (f) {
        fseek(f, 0, SEEK_END);
        length = ftell(f);
        fseek (f, 0, SEEK_SET);

        if (length > 39) {
            fprintf(stderr, "file %s is too big\n", thisdata->file);
            pthread_exit(NULL);
        }

        thiscontent = malloc(length);

        if (thiscontent) {
            fread(thiscontent, 1, length, f);
        } else {
            perror("malloc(3) error");
            pthread_exit(NULL);
        }

        fclose(f);
        thisdata->content = thiscontent;

    } else {
        fprintf(stderr, "cannot open file %s\n", thisdata->file);
        pthread_exit(NULL);
    }

    mutex_res = pthread_mutex_lock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to acquire mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    thisdata->reading = 2;
    fprintf(stdout, "finished reading: %u\n", thisdata->reading);

    mutex_res = pthread_mutex_unlock(&thisdata->read_mutex);
    if (mutex_res != 0) {
        fprintf(stderr, "thread_read_file() failed to release mutex: %s\n", strerror(mutex_res));
        pthread_exit(NULL);
    }

    fprintf(stdout, "content: %s\n", thiscontent);

    return NULL;
}

int main(void) {

    int mutex_err = pthread_mutex_init(&first_file.read_mutex, NULL);

    if (mutex_err != 0) {
        fprintf(stderr, "pthread_mutex_init(3) error: %s\n", strerror(mutex_err));
        exit(EXIT_FAILURE);
    }

    first_file.reading = 1;
    first_file.content = NULL;
    first_file.file = content_file;

    pthread_t thread1;
    int thread_res = pthread_create(&thread1, NULL, thread_read_file, &first_file);
    if (thread_res != 0) {
        fprintf(stderr, "pthread_create(3) error: %s\n", strerror(thread_res));
        exit(EXIT_FAILURE);
    }

    int read_done = 0;
    while (!read_done) {

        mutex_err = pthread_mutex_lock(&first_file.read_mutex);
        if (mutex_err != 0) {
            fprintf(stderr, "pthread_mutex_lock(3) error: %s\n", strerror(mutex_err));
            exit(EXIT_FAILURE);
        }
        /* Reading is finished when first_file.reading == 2 */
        read_done = (first_file.reading == 2);

        if (first_file.reading != 2)
            printf("Still reading, reading: %u\n", first_file.reading);

        mutex_err = pthread_mutex_unlock(&first_file.read_mutex);
        if (mutex_err != 0) {
            fprintf(stderr, "pthread_mutex_unlock(3) error: %s\n", strerror(mutex_err));
        }
    }

    /* Here we don't need to lock because the thread has finished and no other thread is
     * using this struct
     */

    fprintf(stdout, "Read file %s, reading: %u, content: %s\n", first_file.file, first_file.reading, first_file.content);

    free(first_file.content);
    mutex_err = pthread_mutex_destroy(&first_file.read_mutex);
    if (mutex_err != 0) {
        fprintf(stderr, "Warning: Error destroying mutex: %s\n", strerror(mutex_err));
    }

    return 0;
}

请注意,我在main() 的末尾添加了清理代码。即使没有必要(因为程序即将终止),它也可以确保您不会忘记一旦线程终止需要进行什么样的清理。

【讨论】:

  • 感谢您的建议,但您使用 join_res = pthread_join(thread1, NULL);这将在线程完成后阻塞,这正是我想要避免的。我已经用 pthread_join 注释了 join_res 部分并改为使用: while ( first_file.reading != 2 ) { fprintf(stdout,"still reading\n");睡眠(1);这样做有什么问题吗?
  • @MathiasLienard 虽然它在实践中可能有效,但它不是一个好方法,因为读取和写入整数结构字段不一定是原子操作。您应该使用互斥锁来保护它,以确保您始终在first_file.reading 中看到一致的值。我将更新我的答案以展示您如何做到这一点。
  • @MathiasLienard 查看更新后的答案。我进行了一个小测试,它似乎正在工作。
  • 非常感谢您的帮助。我已经绿色检查了你的答案,send_a_fresh_beer 按钮在哪里?
  • @MathiasLienard 呵呵,谢谢 :) 很高兴我能帮上忙!祝您项目顺利并取得成功。
猜你喜欢
  • 2021-06-30
  • 2016-07-31
  • 1970-01-01
  • 2020-03-05
  • 2013-10-23
  • 2011-12-05
  • 1970-01-01
  • 2013-11-15
  • 1970-01-01
相关资源
最近更新 更多