【问题标题】:Using mutex only when collison occurs in threads仅当线程中发生冲突时才使用互斥锁
【发布时间】:2014-09-20 13:33:33
【问题描述】:

我很困惑如何使用 POSIX 来使用 Mutex。考虑以下代码:

void *print_message_function( void *ptr );
pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
main()
{
     pthread_t thread1, thread2,thread3;
     std::string message1 = "Apple";
     std::string message2 = "Orange";
     int  iret1, iret2,iret3;


     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) &message1);
     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) &message1);
     iret3 = pthread_create( &thread3, NULL, print_message_function, (void*) &message2);

     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 
     pthread_join( thread3, NULL); 

     exit(EXIT_SUCCESS);
}

void *print_message_function( void *ptr )
{
    pthread_mutex_lock( &count_mutex );
    int i = 3 ;
    while( i ) { 

        printf("i is %d ...%s \n", i,(*(std::string *)ptr).c_str() );
        i-- ;
        sleep (1) ;
    }
    pthread_mutex_unlock( &count_mutex );
}

线程 1 和线程 2 使用一个公共资源——message1。 Thread3 使用自己的资源——消息 3。 在 STDOUT 上搞乱打印对我来说是可以的。

程序的输出是

i is 3 ...Apple
i is 2 ...Apple
i is 1 ...Apple
i is 3 ...Apple
i is 2 ...Apple
i is 1 ...Apple
i is 3 ...Orange
i is 2 ...Orange
i is 1 ...Orange

正如我们所见,thread3 在最后执行。由于thread3不使用任何公共资源,我怎样才能让它“跳过互斥锁”。仅当两个线程访问同一块内存而不是其他情况时,如何才能启用互斥锁。互斥锁必须是动态的。我什至愿意指定一个变量,该变量不应指向内存中的相同位置以激活锁。 换句话说,我怎样才能创建一个只有在两个线程发生冲突时才被激活的互斥锁。

我知道这可能是一个重复的问题。但我无法找到任何解决这个问题的方法。另外,由于一些环境限制,我不能使用 C++11 STL 线程或 boost。我想要 pthreads 库的帮助。

【问题讨论】:

    标签: c multithreading pthreads mutex


    【解决方案1】:

    现在你的问题是你在同一个对象上做 mutex_lock。像这样的东西有用吗?

    struct DataWithMutex {
       std::string message;
       pthread_mutex_t count_mutex;
    };
    
    void *print_message_function( void *ptr );
    main()
    {
         pthread_t thread1, thread2,thread3;
    
         DataWithMutex data1 = {"Apple", PTHREAD_MUTEX_INITIALIZER};
         DataWithMutex data2 = {"Orange", PTHREAD_MUTEX_INITIALIZER};
         int  iret1, iret2,iret3;
    
    
         iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) &data1);
         iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) &data1);
         iret3 = pthread_create( &thread3, NULL, print_message_function, (void*) &data2);
    
         pthread_join( thread1, NULL);
         pthread_join( thread2, NULL); 
         pthread_join( thread3, NULL); 
    
         exit(EXIT_SUCCESS);
    }
    
    void *print_message_function( void *ptr )
    {
        DataWithMutex* const data = (DataWithMutex*)ptr;
        pthread_mutex_lock( &(data->count_mutex) );
        int i = 3 ;
        while( i ) { 
    
            printf("i is %d ...%s \n", i,data->message.c_str() );
            i-- ;
            sleep (1) ;
        }
        pthread_mutex_unlock( &(data->count_mutex) );
    }
    

    【讨论】:

    • 非常感谢!我的以下问题是,这种方法使用将互斥锁保留在要传递给 pthread_create 的数据中。如果我的数据有一个字符串和一个列表怎么办。我只关心两个线程之间的字符串是否相同而不是列表。即使 list 是由两个线程同时写入的,对我来说也可以。如何确保互斥锁只锁定在字符串而不是列表上。
    • 创建一个只包含您关心的数据的类,并让您的互斥锁成为其中的一部分?
    • 不,但我需要其他数据。它是只读的,但我需要将它传递给调用线程时调用的函数
    • 在这种情况下,在更大的结构中包含指向带互斥的类的指针(或引用,无论您喜欢什么)。
    【解决方案2】:

    我会说,如果您持有互斥锁的时间足够长以至于这是一个问题,那么您可能在代码中做错了什么。只需将互斥锁保持到绝对最小值的时间减少到上面的代码之类的示例中,问题就会得到解决。

    【讨论】:

      【解决方案3】:

      你提到你不能使用 C++11,而你使用的是std::string,所以我假设你可以使用 C++03。

      代码:

      一个解决方案是这样的:

      (实时、可编译和可运行示例:http://coliru.stacked-crooked.com/a/376cfc4b50405333

      #include <iostream>
      #include <pthread.h>
      #include <unistd.h>
      
      class LockGuard {
          public:
              LockGuard(pthread_mutex_t& mutex)
                : m(mutex)
              {
                  pthread_mutex_lock(&m);
              }
      
              ~LockGuard()
              {
                  pthread_mutex_unlock(&m);
              }
          private:
              pthread_mutex_t& m;
      };
      
      class Access {
          public:
              Access(std::string& message)
                : msg(message) {}
      
              virtual std::string read() const
              {
                  return msg;
              }
      
              virtual void write(const std::string& new_msg)
              {
                  msg = new_msg;
              }
      
          protected:
              std::string& msg;
      };
      
      class LockedAccess : public Access {
          public:
              LockedAccess(std::string& message)
                : Access(message)
              {
                  pthread_mutex_init(&mutex, NULL);
              }
      
              std::string read() const
              {
                  LockGuard lock(mutex);
                  return msg;
              }
      
              void write(const std::string& new_msg)
              {
                  LockGuard lock(mutex);
                  msg = new_msg;
              }
      
          private:
              mutable pthread_mutex_t mutex;
      };
      
      void* print_message_function(void* ptr)
      {
          Access* accessor = static_cast<Access*>(ptr);
          int i = 3;
          while (i)
          {
              printf("i is %d ... %s \n", i, accessor->read().c_str());
              i--;
              sleep(1);
          }
          return NULL;
      }
      
      int main()
      {
          std::string message1 = "Apple";
          std::string message2 = "Orange";
          Access unlocked_access(message2);
          LockedAccess locked_access(message1);
      
          pthread_t thread1, thread2, thread3;
          int iret1, iret2, iret3;
      
          iret1 = pthread_create(&thread1, NULL, print_message_function, static_cast<void*>(&locked_access));
          iret2 = pthread_create(&thread2, NULL, print_message_function, static_cast<void*>(&locked_access));
          iret3 = pthread_create(&thread3, NULL, print_message_function, static_cast<void*>(&unlocked_access));
      
          pthread_join(thread1, NULL);
          pthread_join(thread2, NULL);
          pthread_join(thread3, NULL);
      
          return 0;
      }
      

      解释:

      在这里,我们利用:

      1. RAII(或基于作用域的资源管理)确保锁定正确,并且在函数退出时解锁,即使是通过异常退出

      2. 根据我们提供的类型执行不同操作的虚拟调度。

      这个想法是提供一个用于访问消息的类接口,并提供一个派生类,它提供相同的接口,但在底层做一些不同的事情——它需要锁。您的线程函数现在可以继续调用函数 read(),虚拟调度将确定是调用锁定版本还是解锁版本。

      您仍然必须决定资源是否共享,并将其包装在适当的 Access 类中。真的没有办法解决这个问题。编译器和一般系统事先并不知道某些东西只能由一个线程访问。如果你不能提前决定,你能做的最好的事情是为每条消息提供一个互斥锁,这样锁是独立的(即线程 3 不会争用“Oranges”消息的锁,但仍然有锁定它;线程 1 和 2 将争夺“Apples”消息的锁定)。

      在 C 中:

      如果您实际上不能使用 C++,那么您可以通过为每个消息/潜在共享数据块设置一个锁来实现与 C 中大致相似的功能。您可以将数据和锁捆绑到一个结构中,并将指向该结构的指针传递到您的线程中。然后线程必须在访问结构的其他成员之前获取锁。

      锁定粒度:

      最后一点,您应该尝试为您的特定程序确定一个合理的锁定模式;获取锁可能会很昂贵,尤其是在存在争用时。 锁粒度是典型的参考,它指的是有多少代码被锁覆盖。在您的示例中,整个线程函数都被锁覆盖。在我上面的代码中,只涉及对共享变量的访问。这允许竞争线程取得更多进展,但代价是更频繁地获取和释放锁。您需要的模式取决于您的应用程序,您可能需要花一些时间考虑最佳方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-31
        • 1970-01-01
        • 2016-12-02
        • 1970-01-01
        • 1970-01-01
        • 2021-11-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多