Зачем нужен мьютекс в этом случае?

Рейтинг: 1Ответов: 3Опубликовано: 08.04.2015
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#include "get_file.h"
#include "read_text.h"
#define NUM_OF_THREADS 4
#define len 2084

char *arr = new char[len];//Выделяем память под массив
int blocks=len/NUM_OF_THREADS;//В одном потоке
int i,status,s;
pthread_mutex_t mp=PTHREAD_MUTEX_INITIALIZER;

int main(){
    pthread_t threads[NUM_OF_THREADS],main_thread;
    pthread_mutex_init(&mp,NULL);

    pthread_create(&main_thread,NULL,get_array,NULL);
    pthread_join(main_thread,NULL);
    printf("\tRunning %d threads... \n",NUM_OF_THREADS);
    sleep(1);
    for(i=1;i<=NUM_OF_THREADS;i++)
    {
        status=pthread_create(&threads[i],NULL,read_text,(void *)i);
        pthread_join(threads[i],NULL); //Потоки выполняются по очереди
        if(status!=0)
            printf("Thread is not created. Error: %d",status);
    }
    printf("\n \t ---- Threads complete!!! ----\n");
    pthread_mutex_destroy(&mp);
    pthread_exit(NULL); 
}

get_file.h

void *get_array(void *)
{
    pthread_mutex_lock(&mp);//установка блокировки
    sleep(1);
    FILE* fp = fopen("text.txt", "r");
    for(s=0;s<len;s++)
    arr[s]=fgetc(fp);//Считываем по символьно, изменяя глобальную переменную
    fclose(fp);
    printf("\t Read text success!\n");
    pthread_mutex_unlock(&mp);//снятие блокировки
    return NULL;
}

read_text.h

void *read_text( void *k)//Функция для вызова в потоке
{
    int n,num=(int)k;
    for(n=blocks*(num-1);n<blocks*num;n++)
    putchar(arr[n]);
    return NULL;
}

Ответы

▲ 4Принят

Вам тут много уже полезного сказали, но не сказали главного.

Сейчас у Вас мьютекс вообще не используется, поскольку им не пользуются читатели (никто не конкурирует за захват мьютекса -- следовательно нет и не может быть никакой синхронизации (и барьерной тоже)).

Понимаете, сам по себе вызов pthread_mutex_lock(&mp); блокирует доступ именно к mp и никак не влияет на возможность других потоков читать-писать в память. Синхронизация между потоками осуществляется именно (и только) в точках вызова lock с одним и тем же мьютексом.

Т.е. если решили потренироваться в написании многопоточной программы, то увы, Вы решаете не ту задачу.

IMHO было бы интереснее рассмотреть программу с массивом мьютексов (по числу потоков-обработчиков). Представьте, main сначала лочит их все и запускает все потоки (вместе с писателем). Писатель знает количество читателей и поочередно разблокирует мьютексы, когда очередной блок данных уже можно обрабатывать.

И кстати, для разумного решения вашей задачи в параллельном исполнении (предполагаем один писатель - много читателей общего ресурса) Вам надо посмотреть на pthread_rwlock_init/pthread_rwlock_rdlock/pthread_rwlock_wrlock и т.п.

▲ 3

Если я правильно понял логику работы вашей программы, вы в одном потоке хотите читать файл в память (кстати, если делать это побайтово, то получится медленно), а затем в четырёх потоках выводить на экран. Вывод в 4-х потоках превратит информацию в кашу, но, допустим, такова ваша задача.

Мьютекс может пригодиться вам по следующей причине. Вызов sleep(1) не гарантирует, что первый поток прочитает файл целиком. Вот тут мьютекс и помог бы. В принципе, вы могли бы "окружить" им создание потоков для вывода. Тогда потоки вывода не создавались бы, пока не выполнено чтение. Получилась бы этакая барьерная синхронизация. Но это не лучшее решение, как мне кажется. Проще будет переделать программу, чтобы избежать проблем в будущем. Как уже справедливо заметили, отдельный поток для чтения не особо и нужен. Если сделаете всё в main, то нужда в барьерной синхронизации просто отпадёт и мьютекс не будет нужен.

Кроме того, у вас есть несколько недочётов.

Например, pthread_join внутри цикла сразу после запуска потока. В таком виде потоки не будут работать параллельно. Нужно все pthread_join вынести за его пределы в отдельный цикл, например. Да и нужно как-то обработать ошибку при создании потока, а то pthread_join упадёт при попытке дождаться завершения потока, который не был создан.

Следующая проблема - вы передаёте число через преобразование в void*. Это очень ненадёжный способ. Ваша логика понятна: если вы будете передавать &i, то у всех потоков он будет меняться с изменением i. Но это не очень честный трюк - выдавать за адрес то, что адресом не является.

▲ 2
  1. В Вашем примере главный поток, который заполняет массив, завершается до того, как будут порождены потоки, которые этот массив выводят, поэтому мьютекс в данном случае Вам не нужен вообще, т.к. остальные потоки - "читатели", этот глобальный массив менять не пытаются. Текущая Ваша реализация, насколько я понимаю, параллельности не подразумевает вообще, так как сразу же за созданием каждого потока следует pthread_join().
  2. Если бы Вы не завершали главный поток до порождения потоков-читателей, и хотели выводить данные одновременно из нескольких потоков, то какие-то элементы синхронизации Вам определенно понадобились бы, так как, в противном случае, потоки-читатели могли бы отработать до полного заполнения данного массива и вывести его в неконсистентном состоянии. Однако, использованию мьютекса в данном случае я бы предпочла использование блокировки чтения-записи: главный поток захватывал бы блолкировку на запись, и отпускал по завершению обработки, а остальные потоки захватывали бы ее на чтение, и таким образом, могли бы выводить данные одновременно (а не последовательно, как если бы захватывали единственный мьютекс).