c++ - 在线程之间共享资源(文件,互斥体)

标签 c++ linux multithreading pthreads

我正在编写一个C++类,它控制GNU/Linux上的串行端口(RS232),并且我已经决定(基于关于Linux上的异步I/O的文章的数量)来使用多个线程,其中使用阻塞的读/写功能。
我想知道,如何正确地共享某些资源:串行端口的文件描述符和互斥锁。到目前为止,我发现的所有示例都将FD和mutex创建为全局变量,这样可以确保它们将被共享到创建的任何新线程中。
但是,由于我正在创建一个类,并且希望将FD和mutex作为私有变量,所以这并不是我真正的选择。
1)文件描述符:
void * arg中为pthread_create共享FD就足够了吗?还是最好在每个线程中打开串行端口?或许是第三种选择?
2)互斥:
仅仅在void * arg中为pthread_create共享互斥地址就足够了吗?还是我需要创建一个共享内存段,就像我在使用信号量并在多个进程之间共享它们一样?
我知道这个问题问得不太好,所以我提前表示同意。
也谢谢你的帮助。
编辑:
我在网上看到的代码是这样的:

/* Global variables */
pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
int file_descriptor = 0;

struct my_data_t
{
    char buffer[BUFFER_SIZE];
    int id;
};

void * thread_routine(void *aData_)
{
    my_data_t * data = (my_data_t*) aData_;
    int err = 0; 
    pthread_mutex_lock(&thread_mutex);

    err = read(file_descriptor, data->buffer, BUFFER_SIZE);

    pthread_mutex_unlock(&thread_mutex);
    /* executes thread routine */
    return;
}

int main(int argc, char * argv[])
{
    pthread_t new_thread;
    pthread_attr_t new_thread_info;
    my_data_t thread_data;
    thread_data.id = 1;
    /* setting up thread */

    file_descriptor = open(DEVICE_NAME, O_RDWR );
    pthread_mutex_init(&thread_mutex, NULL);

    pthread_create(&new_thread, &new_thread_info, &thread_routine, (void*) &thread_data);

    pthread_join(new_thread, NULL);

    return 0;
}

文件描述符和互斥量都作为全局变量创建,因此在新线程中可用。我需要的是用新线程共享这些资源的方法,当它们被封装在这样的C++类中时:
class Example
{
private:
    pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
    int file_descriptor = 0;
    char buffer[BUFFER_SIZE];
public:
    Example(/* args */);
    ~Example();
    ...
};

我想到的可能是通过void * agr中的pthread_create()参数在如下结构中传递指向原始文件描述符和互斥对象的指针:
struct data_for_thread_t
{
    char **buffer;
    int * p_file_descriptor;
    pthread_mutex_t * p_thread_mutex; 
};

或者要在新线程中打开所需的文件,而不是使用pthread_mutex,我将使用经典的POSIX信号量并通过shm共享它。

最佳答案

让我来解释一下为什么你在网上找到的代码是按原样构造的:最可能的原因是作者没有足够的技能和经验。听起来很刺耳,但恐怕我甚至可以为这种观点提供很好的论据。现在,因为。。。
首先,为了澄清一点,线程是“控制点”或“独立执行”。它“生活”在一个进程中,可能还有其他线程。在该进程中,它与该进程中的所有其他线程共享一个内存空间。这意味着没有内存只能由一个线程访问!这意味着您可以从另一个线程访问一个线程的局部变量。但是,另一个线程通常不知道这些变量的位置,所以它实际上是不知道的。保持这些信息的分隔会有帮助,因为您不想意外地弄乱其他线程的东西。但是,没有必要为了使它们可访问而使其全局化。您可以使用您传递的地址的局部变量。
现在,在某些情况下,您希望在线程之间共享信息。与函数相比,如果你想访问一些数据,你基本上有两种方法:一种是将数据作为参数传递给函数,另一种是将数据作为全局存储。现在,线程与函数没有太大区别,只是通过pthread_create()调用的函数只接受一个空指针。std::thread比这更灵活,您可以以一种类型安全的方式轻松传递多个不同的参数。但是,全局变量仍然是可选的。
为什么上面提到的代码不好?关键是它在全局变量(互斥量、文件描述符)和参数(我的数据)之间混合使用,而且没有任何原因。一个更明智的方法是要么将它们全部设为全局,要么将它们全部作为参数传递。更喜欢后者,它使代码更易于单独测试。有关混合物特别糟糕的原因的解释,请参见下文。
而且,它是坏的,因为它没有显示任何东西。它显示了一个无缘无故启动的线程和执行无用操作(互斥锁、互斥锁)的线程。互斥锁是完全无用的,因为在线程运行时,没有其他代码触及文件描述符或缓冲区!由于没有其他代码触及数据,因此它不共享,也不需要与互斥锁同步访问。在这里等待线程完成就足够了。
但是,假设您有一些共享数据,这些数据由多个线程并发访问。为了实现这一点,您可以通过互斥锁同步访问。因为没有什么能真正阻止您在不锁定互斥锁的情况下访问数据,所以错误很容易发生。避免错误的策略是让线程之间的数据共享变得明显。此外,要清楚哪个互斥体负责保护这些数据。实现这一目标有两种常见的方法。第一种方法是简单地将数据和互斥锁包装在一个结构中,这样很明显它们属于一起。第二种方法是用一个简单的注释来记录。这也迫使你在头脑中制定一个计划,这是一件好事。特别是,这样就不太可能共享数据但没有互斥锁,或者有两个互斥锁!这两个非常简单的策略都没有被你在网上找到的代码的作者使用,这是另一个反对它的论点。
最后,示例代码忽略了也可以从线程返回某些内容,就像从函数返回某些内容一样。这可以通过pthread_join()的第二个参数来检索。使用std::thread,您可以获得更多选项。如果示例代码使用了该特性,那么它可能会返回包含文件中数据的缓冲区。然后,只有通过调用pthread_join()才能执行同步。
笔记:
注意,在C++中,也可以在命名空间中拥有非全局变量或类内的静态变量,但这类似于当前问题的全局性。
除了互斥之外,还有一些原子操作有时可以用作互斥的替代。记住这一点,作为另一件要学习的事情。
避免争用资源的线程。如果他们这样做了,他们往往会轮流阻止对方的进展,所以你既不能得到独立执行的好处,也不能从并行执行中得到加速的好处。更喜欢协作的线程。
如果可以,请避免共享数据。更喜欢独家所有权。您可以设置任务并将其交给线程执行。完成后,线程将给出结果。尤其是这一策略。由于数据不是共享的,而是传递的,因此不需要任何访问同步和它所带来的开销。如前所述,这也是一种合作。
C++允许你做很多聪明的事情。例如,可以包装一个数据结构和一个互斥对象,并且只允许在锁定互斥对象的情况下进行访问。查看智能指针实现及其std::unique_ptr重载以获取灵感。
您绘制的类具有构造函数和析构函数,但不要忘记删除复制构造函数和赋值运算符。看看“三定律”。
您使用operator->的方法很接近,您只需要使用指针放弃间接寻址。一般来说,避免C++中的指针。
作为建议,请尝试正确地实现示例(在后台读取文件)。当你有工作时,提交代码到code review.stackexchange.com进行审查。不过,请先检查它们的指导原则,因为它们与堆栈溢出的指导原则有很大不同。

关于c++ - 在线程之间共享资源(文件,互斥体),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58594723/

相关文章:

python - 释放python中所有使用的内存

java - 比较两个使用断言转换为对象的json列表

c++ - 使用 vector 作为多线程队列是否安全?

检查线程是否未使用 C

c++ - 如何让 Cygwin 上的 gcc 定义 _WIN32?

c++ - 与legendre公式相关的程序异常

C++ 无锁队列因多线程而崩溃

c++ - 以下 g++ 类构造中的错误是什么?

linux - 如何找到行和单词的总和

python - 在 Python 队列中伪造一个插入