【问题标题】:Why doesn't the semaphore block the second thread? (C)为什么信号量不阻塞第二个线程? (C)
【发布时间】:2020-02-29 10:36:56
【问题描述】:

我想编写一个简单的 C 程序来更好地理解信号量。有两个线程,它们都调用相同的函数。第一个线程,增加全局变量,第二个线程,减少全局变量。

我试图在第一个线程完成工作之前阻止第二个线程使用该函数。但我仍然得到错误的答案:-2000。看起来两个线程都有偏移量-1。我该如何解决这个问题,使输出始终为 0?我认为函数内部的 sem_wait 应该阻塞第二个线程,直到第一个线程结束。因此偏移量应该保持为 1。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define NUM_LOOPS 1000
long long counter = 0;
sem_t sem;
sem_t sem1;
void* counting_thread(void* arg) {
    sem_wait(&sem);
    int offset = *(int*) arg;
    int i;
    for(i = 0; i < NUM_LOOPS; i++){
        //sem_wait(&sem);
        counter += offset;
        printf("offset = %d\n", offset1);


        //sem_post(&sem);
    }
    sem_post(&sem);

    pthread_exit(NULL);
}

int main() {
    sem_init(&sem, 0, 1);

    pthread_t th1;
    int offset = 1;
    pthread_create(&th1, NULL, &counting_thread, &offset);
    //sem_post(&sem1);

    pthread_t th2;
    offset = -1;
    pthread_create(&th2, NULL, &counting_thread, &offset);

    pthread_join(th1, NULL);
    pthread_join(th2, NULL);

    printf("Finnal counter value: %lld\n", counter);
    sem_destroy(&sem);
    return 0;
}

【问题讨论】:

  • pthread_create 将指向变量的指针作为参数传递给线程。你指向同一个变量,offset 用于两个线程,这意味着根据你重新分配它的时间,它们可能会得到相同的值传入。尝试为第二个线程创建一个单独的变量,而不是重新分配一个已经传递的变量到第一个。
  • 是的,因为线程使用相同的地址空间,这就是问题所在。但是应该有一个只使用信号量的解决方案。
  • 您对offset 的重新分配发生在信号量锁之外(以及启动的线程之外),因此无论您在线程内锁定多少,这都是一种竞争条件。
  • 即使我在第二个线程的函数调用和变量更新周围放置一个信号量(这样偏移量不会被更新),我得到一个错误的结果。
  • 您的结果最有可能发生的事情(对比赛条件的保留相当不确定)是; 1)您将 1 分配给偏移量。 2)您告诉线程 1 在未来某个时间启动 3)您将 -1 分配给偏移量 4)您告诉线程 2 在未来某个时间启动 5)线程都启动,并且都获得偏移量的当前值 -1作为参数。

标签: c multithreading operating-system pthreads semaphore


【解决方案1】:

根据pthread_create man page

注意 关于线程 ID 的更多信息,请参见 pthread_self(3) 由 pthread_create() 在 *thread 中返回。除非实时调度 正在采用策略,在调用 pthread_create() 之后, 不确定下一个线程是调用者还是新线程 执行。

不保证谁将执行下一个。 也许对你来说,main 线程正在执行下一个线程并在新线程之前设置offset to -1,使其从offset 读取更新值,即-1


您可以使用条件变量进行同步。

例如:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define NUM_LOOPS 1000
long long counter = 0;
sem_t sem;
sem_t sem1;
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; 
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 


void* counting_thread(void* arg) {
    sem_wait(&sem);
    int offset = *(int*) arg;
    pthread_cond_signal(&cond1); 

    int i;
    for(i = 0; i < NUM_LOOPS; i++){
      counter += offset;
        printf("offset = %d\n", offset);

  }
    sem_post(&sem);

    pthread_exit(NULL);
}

int main() {
    sem_init(&sem, 0, 1); 

    int offset = 1;
    pthread_t th1;
    pthread_create(&th1, NULL, &counting_thread, &offset);


    pthread_cond_wait(&cond1, &lock);
    pthread_t th2;
    offset = -1;
    pthread_create(&th2, NULL, &counting_thread, &offset);

    pthread_join(th1, NULL);
    pthread_join(th2, NULL);

    printf("Finnal counter value: %lld\n", counter);
    sem_destroy(&sem);
    return 0;
}

【讨论】:

  • 我总是得到 -2000 的结果,所以我认为操作系统恰好在第一个线程启动后立即运行第二个线程。偏移值甚至不会是 1。
  • 我总是得到 -2000 的结果,所以我认为操作系统恰好在第一个线程启动后立即运行第二个线程。偏移值甚至不会是 1。
  • @lalalal 尝试使用我在示例中展示的条件变量
  • 这是正确的答案,但我仍然想知道为什么不在第二个线程调用周围放置 sem_wait 不起作用。所以基本上这个条件case和sem_Wait有什么区别。因为据我了解,条件语句等待第一个线程结束。 sem_wait 不是一回事吗
  • @lalalal 否,sem_wait 有可能main 线程将获取semaphore 并继续更新offset to -1 然后new-thread 将获取semaphore。但是使用conditional 变量main 线程将等待该互斥体,直到new thread 发出信号。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-27
  • 2016-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-02
相关资源
最近更新 更多