#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> char password[] = "password"; char str[30]; pthread_cond_t wake_up; pthread_cond_t wake_up1; pthread_mutex_t mut; void *enter_pass(void *a) { while(1) { printf("Enter password:\n"); pthread_mutex_lock(&mut); scanf("%s", str); //pthread_mutex_unlock(&mut); pthread_cond_signal(&wake_up); pthread_mutex_unlock(&mut); pthread_cond_wait(&wake_up1, &mut); } } void *check_pass(void *a) { while(1) { pthread_mutex_lock(&mut); pthread_cond_wait(&wake_up, &mut); //pthread_mutex_lock(&mut); if(!strcmp(str , password)) { pthread_mutex_unlock(&mut); printf("true\n"); exit(EXIT_SUCCESS); } else printf("Wrong password, try another\n"); pthread_cond_signal(&wake_up1); pthread_mutex_unlock(&mut); } } int main(void) { pthread_t thread1, thread2; int rc; long a; pthread_mutex_init(&mut, NULL); pthread_cond_init (&wake_up, NULL); pthread_cond_init (&wake_up1, NULL); rc = pthread_create(&thread1, NULL, enter_pass, (void *)a); if (rc) { printf("ERROR: %s\n", strerror(errno)); exit(EXIT_FAILURE); } rc = pthread_create(&thread2, NULL, check_pass, (void *)a); if (rc) { printf("ERROR: %s\n", strerror(errno)); exit(EXIT_FAILURE); } pthread_join(thread1, NULL); pthread_join(thread2, NULL); } 

Yes, it became so much better (as I could forget), but if I enter it wrong once, it hangs after the second input.

  • "parameter name omitted". Well? Is it really not clear from the text of the error what is the matter? In C, there are no unnamed parameters in Fonsky. All parameters must have explicit names. - AnT
  • No, still not that. You must release the mutex after the signal. - VladD
  • And to continue the cycle, you need the second cond.var. - VladD
  • Well, it is necessary to wait for the first signal, too, with a captured mutex. - VladD
  • one
    Ok, now you have created threads, but then exit the program. In this case, everything is clear, dying. You need to wait for the threads to complete. Use pthread_join . - VladD

3 answers 3

  • First, pthread_mutex_t objects should be initialized before use, either using PTHREAD_MUTEX_INITIALIZER or pthread_mutex_init . Similarly, pthread_cond_t .

  • Secondly, the thread calling pthread_cond_wait is required to own the corresponding mutex at the time of this call. I do not see anywhere in you attempts to capture the mut , respectively, your calls to pthread_cond_wait deliberately incorrect.

    The “traditional” protocol for using pthread_cond_wait looks like

     pthread_mutex_lock(&mut); ... pthread_cond_wait(&wake_up, &mut); ... pthread_mutex_unlock(&mut); 
  • Third, the stream functions must be declared void (void *) , not void () .

  • Fourthly, as @VladD correctly noted, the completion of the "main" thread of your program (in which main is executed), immediately kills the entire process with all other threads, without waiting for them to complete. They do not even have time to croak. The main function should wait for your threads to terminate via pthread_join .

  • one
    I think the fall is most likely caused by access to uninitialized variables. PTHREAD_MUTEX_INITIALIZER unlikely to consist entirely of zero bytes. - VladD
  • @VladD: Hard to say ... Depends on the implementation. I remember that in the dirty code I encountered synchronization primitives relying on zero initialization of static objects, and such code even seemed to "work." But no doubt, it is necessary to explicitly initialize synchronization primitives, of course. - AnT

In your scanf task, which is waiting for data entry, it can serve as a “natural synchronizer” and then the program is somewhat simplified.

 char password[] = "password"; char str[30]; pthread_cond_t wake_up; pthread_mutex_t mut; void *enter_pass(void *a) { while(1) { printf("Enter password:\n"); scanf("%s", str); pthread_mutex_lock(&mut); pthread_cond_signal(&wake_up); pthread_mutex_unlock(&mut); } } void *check_pass(void *a) { while(1) { pthread_mutex_lock(&mut); pthread_cond_wait(&wake_up, &mut); pthread_mutex_unlock(&mut); if(!strcmp(str , password)) { printf("true\n"); exit(EXIT_SUCCESS); } printf("Wrong password, try another\n"); } } int main(void) { pthread_t thread1, thread2; pthread_mutex_init(&mut, NULL); pthread_cond_init (&wake_up, NULL); pthread_create(&thread1, NULL, enter_pass, 0); pthread_create(&thread2, NULL, check_pass, 0); pthread_join(thread1, NULL); pthread_join(thread2, NULL); } 

Now only one mutex and one condition variable are needed.

However, it is impossible to rely on such "synchronization" of interaction between threads. @VladD in his response showed how to synchronize the start of work streams from main.

And here is another way to "handshake" without external synchronization. Add a couple of variables and a simple function.

 int checker = 0, reader = 0; void handshake (pthread_mutex_t *mutex, pthread_cond_t *cond, int *v1, int *v2) { pthread_mutex_lock(mutex); *v1 = 1; while (!*v2) pthread_cond_wait(cond, mutex); pthread_cond_signal(cond); *v2 = 0; pthread_mutex_unlock(mutex); } 

And now we insert its call to the beginning of our functions.

 void *enter_pass(void *a) { handshake(&mut, &wake_up, &reader, &checker); ... } 

And in check_pass () we will rearrange variables

 void *check_pass(void *a) { handshake(&mut, &wake_up, &checker, &reader); ... } 

Everything. Now the functions will wait for each other and the program can be run, for example,

 echo abc rireii password | ./a.out 

Until the flow with check_pass is ready, scanf will not be called.

UPDATE

After discussion in the comments, “brushed” the program, now both threads correctly exit, returning to main, incl. at the end of the input (EOF), the reader waits until the checker allows him to enter the next password (or says that it’s enough to read - the password is correct). To do this, add 3 variables (go, pass (continued reader) and check_it (continued checker)).

 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> char password[] = "password"; char str[30]; pthread_cond_t wake_up; pthread_mutex_t mut; volatile int checker = 0, reader = 0, go = 1, pass = 0, check_it = 0; void handshake (pthread_mutex_t *mutex, pthread_cond_t *cond, volatile int *v1, volatile int *v2) { pthread_mutex_lock(mutex); *v1 = 1; while (!*v2) pthread_cond_wait(cond, mutex); pthread_cond_signal(cond); *v2 = 0; pthread_mutex_unlock(mutex); } void *enter_pass(void *a) { handshake(&mut, &wake_up, &reader, &checker); puts("reader ok"); while (go) { printf("Enter password:\n"); int rc = scanf("%s", str); pthread_mutex_lock(&mut); if (rc != 1) go = 0; check_it = 1; // send to checker signal "password ready..." pthread_cond_signal(&wake_up); pass = 0; // prepair to wait checker's signal while (!pass) // wait checker send "continue..." (set pass = 1) pthread_cond_wait(&wake_up, &mut); pthread_mutex_unlock(&mut); } puts("reader fin"); } void *check_pass(void *a) { handshake(&mut, &wake_up, &checker, &reader); puts("checker ready"); while (go) { pthread_mutex_lock(&mut); while(!check_it) // wait scanf in reader done (and reader set check_it = 1) pthread_cond_wait(&wake_up, &mut); check_it = 0; // reset variable for next iteration if (go) { if(!strcmp(str , password)) { printf("true\n"); go = 0; } else puts("Wrong password, try another"); } pass = 1; // send to reader signal "continue..." pthread_cond_signal(&wake_up); pthread_mutex_unlock(&mut); } puts("checker fin"); } int main (int ac, char *av[]) { pthread_t thread1, thread2; pthread_mutex_init(&mut, NULL); pthread_cond_init (&wake_up, NULL); pthread_create(&thread1, NULL, enter_pass, 0); pthread_create(&thread2, NULL, check_pass, 0); pthread_join(thread1, NULL); pthread_join(thread2, NULL); return puts("End"), fclose(stdout) == EOF; } 

Pay attention to the volatile attributes of variables shared by different threads (they are also in the handshake () arguments).
By the way, the handshake function is disposable , i.e. threads can safely use it only when external (before they are started) initialization of checker and reader variables.

If something is unclear, ask, I will try to explain.

  • You seem to have a check_pass on check_pass twice in a row. - VladD
  • And the idea of ​​a handshake cool, I will steal it for myself. - VladD
  • @VladD, thanks, I'll throw it out now. - avp
  • One more thing (sorry for the tediousness): enter_pass starts entering a new password potentially before the end of the test in another thread (and even if this password is not actually needed). - VladD
  • @VladD, yes, perhaps this is wrong. It would be necessary to make waiting for a response from check_pass through some kind of shared variable in the same way as handshake and exit the loop with a successful password. - avp

To get started, look into the documentation :

The pthread_cond_wait() and the pthread_cond_timedwait() functions are used. Undefined behavior will result.

The idea of ​​condition variable is that, in addition to owning a resource, you want to signal the occurrence of an event and transfer ownership of that same resource to another because. In your case, the resource is a password string (which, by the way, cannot be used from different threads without being protected by a mutex). Therefore, logically, without the capture of a mutex is not enough.


In fact, you were close. I renamed the variables and corrected the code.

An important change: pthread_cond_wait only wakes up threads that are already waiting for conditions, and not those that will wait for it in the future! This means that we are obliged to ensure that the password is ready to wait before sending a signal - which means that the thread reading the password starts working (since this thread immediately captures the mutex and does not allow the testing thread to start waiting!).

 #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> char password[] = "password"; char str[30]; pthread_cond_t have_password; // условие: есть новый пароль pthread_cond_t need_next_password; // условие: нужен новый пароль pthread_cond_t evaluator_started; // условие: поток проверки паролей стартовал pthread_mutex_t mut; size_t input_password_no = 0; size_t checked_password_no = 0; int checker_started = 0; void *enter_pass(void *a) { size_t password_length; while(1) { printf("Enter password:\n"); // пользуемся разделяемыми данными - получили мьютекс pthread_mutex_lock(&mut); // scanf небезопасен fgets(str, sizeof(str), stdin); // убираем финальный \n, если он есть password_length = strlen(str); if (password_length > 0 && str[password_length - 1] == '\n') str[password_length - 1] = '\0'; // выставляем новый номер пароля, чтобы проверяющий поток // увидел, что он проснулся не просто так // поскольку мы обращаемся к разделяемой переменной, всё // происходит под мьютексом input_password_no++; // отправляем сигнал под мьютексом pthread_cond_signal(&have_password); // здесь можно было бы отпустить мьютекс, но нам тут же // понадобится взять его снова. так что можно сэкономить // на бесполезной операции //pthread_mutex_unlock(&mut); //pthread_mutex_lock(&mut); // ожидаем, пока не понадобится новый пароль // на время ожидания мьютекс будет автоматически отпущен while (input_password_no != checked_password_no) // на время ожидания мьютекс будет автоматически отпущен pthread_cond_wait(&need_next_password, &mut); // дождались - отпускаем мьютекс pthread_mutex_unlock(&mut); } } void *check_pass(void *a) { // получаем мьютекс... pthread_mutex_lock(&mut); // ... чтобы сигнализировать главному потоку о том, что мы стартовали checker_started = 1; pthread_cond_signal(&evaluator_started); // но мьютекс пока не отпускаем! while(1) { // вот здесь мы начали ждать, и отпустили мьютекс // при первом пробеге именно тут главный поток получит наконец управление // и запустит поток, вводящий данные // проверяем заодно, а не случайно ли мы проснулись while (input_password_no == checked_password_no) pthread_cond_wait(&have_password, &mut); // когда мы пришли сюда, у нас есть новый пароль // проверяем его под мьютексом... int password_ok = !strcmp(str, password); // и отпускаем мьютекс, как только нам больше не нужны разделяемые данные! pthread_mutex_unlock(&mut); // если пароль в порядке, завершаем работу потока if (password_ok) { printf("true\n"); return NULL; } else { printf("Wrong password, try another\n"); } // иначе снова берём мьютекс pthread_mutex_lock(&mut); // ... и сигнализируем, что нужен следующий пароль checked_password_no = input_password_no; pthread_cond_signal(&need_next_password); } } int main(void) { pthread_t input_thread, check_thread; int rc; // инициализация pthread_mutex_init(&mut, NULL); pthread_cond_init (&have_password, NULL); pthread_cond_init (&need_next_password, NULL); pthread_cond_init (&evaluator_started, NULL); // теперь нам нужно дождаться момента, когда стартует поток проверки // захватываем мьютекс // поскольку мьютекс у нас, поток не сможет просигнализировать о старте // до того, как мы начнём слушать pthread_mutex_lock(&mut); // запускаем поток rc = pthread_create(&check_thread, NULL, check_pass, NULL); if (rc) { printf("ERROR: %s\n", strerror(errno)); exit(EXIT_FAILURE); } else { // начинаем ждать сообщения о старте потока pthread_cond_wait(&evaluator_started, &mut); // только тут поток сможет получить мьютекс и просигнализировать // нам о старте // в этой точке поток проверки стартовал, и уже начал слушать // условие have_password (потому что раньше он не отпускал мьютекс, // а значит, мы не вернулись бы из wait) } // отпускаем мьютекс, он больше не нужен тут pthread_mutex_unlock(&mut); // и запускаем вводящий поток rc = pthread_create(&input_thread, NULL, enter_pass, NULL); if (rc) { printf("ERROR: %s\n", strerror(errno)); exit(EXIT_FAILURE); } // пусть потоки себе перебрасываются условиями, мы ждём здесь окончания // работы проверяющего потока pthread_join(check_thread, NULL); // если проверяющий поток закончился, можно завершать программу // это прихлопнет и вводящий поток тоже (он должен сейчас ждать // запроса на следующий пароль). // если проверяющий поток } 

Pay attention to the fight against spurious wakeup. Waiting on the condition variable may end prematurely by chance, so you need to set the appropriate flags and check them.