在线程之间共享资源(文件、互斥锁)
Sharing resources(files, mutexes) between threads
我正在编写一个 C++ class,它控制 GNU/Linux 上的串行端口 (RS232),我已经决定(基于 [=] 上有关 Async I/O 的文章数量55=]) 使用多个线程,其中将使用阻塞 read/write 函数。
我想知道如何正确共享某些资源:串行端口和互斥体的文件描述符。到目前为止,我发现的所有示例都将 FD 和 Mutexes 创建为全局变量,这确保它们将共享到任何创建的新线程中。
然而,由于我正在创建一个 class 并希望将 FD 和互斥锁作为私有变量,这对我来说并不是一个真正的选择。
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 中时,如下所示:
class Example
{
private:
pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
int file_descriptor = 0;
char buffer[BUFFER_SIZE];
public:
Example(/* args */);
~Example();
...
};
我想到的可能性是通过 pthread_create()
中的 void * agr
参数传递指向原始文件描述符和互斥体的指针,结构如下:
struct data_for_thread_t
{
char **buffer;
int * p_file_descriptor;
pthread_mutex_t * p_thread_mutex;
};
或者在新线程中打开所需的文件而不是使用 pthread_mutex 我会使用 classic POSIX 信号量并通过 shm
共享它。
让我来解释一下为什么您在网上找到的代码是这样构造的:最可能的原因是作者没有足够的技能和经验。听起来很刺耳,但恐怕我什至可以为那个观点提供很好的论据。现在,由于...
首先澄清一点,线程是"point of control"或"independent execution"。它 "lives" 在一个进程中,可能还有其他线程。在该进程中,它与该进程中的所有其他线程共享内存 space。这意味着没有内存只能由一个线程独占访问!这意味着您可以从另一个线程访问一个线程的局部变量。但是,另一个线程通常不知道这些变量的位置,因此实际上也不知道。将这些信息分开会有所帮助,因为您不想不小心弄乱其他线程的东西。但是,没有必要为了使它们易于访问而将其全局化。您可以使用您传递的地址的局部变量。
现在,在某些情况下,您希望在线程之间共享信息。与函数相比,如果你想在那里访问一些数据,你基本上有两种方法:一种方法是将数据作为参数传递给函数,另一种方法是将数据存储为全局。现在,线程与函数没有太大区别,只是通过 pthread_create()
调用的函数只接受一个 void 指针。 std::thread
比这灵活得多,您可以轻松地以类型安全的方式传递多个不同的参数。但是,全局变量仍然是选项。
为什么提到的代码不好?关键是它混合使用了全局变量(互斥体、文件描述符)和参数(my_data_t),而且没有任何理由。一种更明智的方法是将它们全部设为全局或将它们全部作为参数传递。更喜欢后者,它使代码更容易单独测试。有关为什么混合物特别糟糕的解释,请参见下文。
此外,它很糟糕,因为它没有展示任何东西。它显示了一个线程无缘无故地启动,并且该线程正在执行无用的操作(互斥锁、互斥锁解锁)。互斥量完全没用,因为当线程处于 运行 时,没有其他代码会接触文件描述符或缓冲区!由于没有其他代码接触数据,因此它不会被共享,也不需要与互斥锁同步访问。在这里等待线程完成就足够同步了。
但是,我们假设您有一些被多个线程同时访问的共享数据。为了做到这一点,您可以通过互斥体同步访问。由于没有什么能真正阻止您在不锁定互斥量的情况下访问数据,因此很容易出错。避免错误的一种策略是使该数据在线程之间共享变得显而易见。此外,明确哪个互斥量负责保护此数据。有两种常见的方法可以实现这一点。第一种方法是将数据和互斥量简单地包装在一个结构中,这样很明显它们属于一起。第二种方法是用简单的注释记录下来。这也迫使你在脑海中制定计划,这是一件好事。特别是,您不太可能共享数据但没有互斥量或者您有两个互斥量!您在网上找到的代码的作者都没有采用这两种非常简单的策略,这是反对它的另一个论据。
最后,示例代码忽略了您也可以 return 来自线程的内容,就像您 return 来自函数的内容一样。这可以通过 pthread_join()
的第二个参数来检索。使用 std::thread
,您可以获得更多选择。如果示例代码使用了该功能,它可以 returned 缓冲区填充文件中的数据。然后仅通过调用 pthread_join()
.
来执行同步
备注:
- 请注意,在 C++ 中,您还可以在名称 space 中包含一个非全局变量,或者在 class 中包含一个静态变量,但这类似于手头问题的全局变量.
- 除了互斥之外,还有一些原子操作有时可以用来替代互斥。请记住这一点作为另一件要学习的东西。
- 避免竞争资源的线程。如果他们这样做,他们往往会轮流阻止对方的进展,因此您既无法获得独立执行的好处,也无法获得并行执行加速的好处。更喜欢合作的线程。
- 如果可以,请避免共享数据。相反,更喜欢独占所有权。您可以设置一个任务并将其交给线程执行。完成后,线程会为您提供结果。特别是
std::unique_ptr
适合这种策略。由于数据不是共享的而是传递的,因此它不需要任何访问同步及其隐含的开销。这也更多的是上面所说的合作
- C++ 允许你做很多聪明的事情。例如,您可以包装一个数据结构和一个互斥锁,并且只允许在锁定互斥锁的情况下进行访问。查看智能指针实现及其
operator->
重载以获得灵感。
- 你画的class有构造函数和析构函数,但不要忘记删除复制构造函数和赋值运算符。查看 "Law of Three".
- 您使用
struct data_for_thread_t
的方法很接近,您只需要删除指针的间接访问。通常,避免在 C++ 中使用指针。
- 作为建议,请尝试正确实现示例(在后台读取文件)。运行后,请在 codereview.stackexchange.com 提交代码以供审核。不过,请务必先查看他们的指南,因为它们与 Stack Overflow 的指南大不相同。
我正在编写一个 C++ class,它控制 GNU/Linux 上的串行端口 (RS232),我已经决定(基于 [=] 上有关 Async I/O 的文章数量55=]) 使用多个线程,其中将使用阻塞 read/write 函数。
我想知道如何正确共享某些资源:串行端口和互斥体的文件描述符。到目前为止,我发现的所有示例都将 FD 和 Mutexes 创建为全局变量,这确保它们将共享到任何创建的新线程中。
然而,由于我正在创建一个 class 并希望将 FD 和互斥锁作为私有变量,这对我来说并不是一个真正的选择。
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 中时,如下所示:
class Example
{
private:
pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER;
int file_descriptor = 0;
char buffer[BUFFER_SIZE];
public:
Example(/* args */);
~Example();
...
};
我想到的可能性是通过 pthread_create()
中的 void * agr
参数传递指向原始文件描述符和互斥体的指针,结构如下:
struct data_for_thread_t
{
char **buffer;
int * p_file_descriptor;
pthread_mutex_t * p_thread_mutex;
};
或者在新线程中打开所需的文件而不是使用 pthread_mutex 我会使用 classic POSIX 信号量并通过 shm
共享它。
让我来解释一下为什么您在网上找到的代码是这样构造的:最可能的原因是作者没有足够的技能和经验。听起来很刺耳,但恐怕我什至可以为那个观点提供很好的论据。现在,由于...
首先澄清一点,线程是"point of control"或"independent execution"。它 "lives" 在一个进程中,可能还有其他线程。在该进程中,它与该进程中的所有其他线程共享内存 space。这意味着没有内存只能由一个线程独占访问!这意味着您可以从另一个线程访问一个线程的局部变量。但是,另一个线程通常不知道这些变量的位置,因此实际上也不知道。将这些信息分开会有所帮助,因为您不想不小心弄乱其他线程的东西。但是,没有必要为了使它们易于访问而将其全局化。您可以使用您传递的地址的局部变量。
现在,在某些情况下,您希望在线程之间共享信息。与函数相比,如果你想在那里访问一些数据,你基本上有两种方法:一种方法是将数据作为参数传递给函数,另一种方法是将数据存储为全局。现在,线程与函数没有太大区别,只是通过 pthread_create()
调用的函数只接受一个 void 指针。 std::thread
比这灵活得多,您可以轻松地以类型安全的方式传递多个不同的参数。但是,全局变量仍然是选项。
为什么提到的代码不好?关键是它混合使用了全局变量(互斥体、文件描述符)和参数(my_data_t),而且没有任何理由。一种更明智的方法是将它们全部设为全局或将它们全部作为参数传递。更喜欢后者,它使代码更容易单独测试。有关为什么混合物特别糟糕的解释,请参见下文。
此外,它很糟糕,因为它没有展示任何东西。它显示了一个线程无缘无故地启动,并且该线程正在执行无用的操作(互斥锁、互斥锁解锁)。互斥量完全没用,因为当线程处于 运行 时,没有其他代码会接触文件描述符或缓冲区!由于没有其他代码接触数据,因此它不会被共享,也不需要与互斥锁同步访问。在这里等待线程完成就足够同步了。
但是,我们假设您有一些被多个线程同时访问的共享数据。为了做到这一点,您可以通过互斥体同步访问。由于没有什么能真正阻止您在不锁定互斥量的情况下访问数据,因此很容易出错。避免错误的一种策略是使该数据在线程之间共享变得显而易见。此外,明确哪个互斥量负责保护此数据。有两种常见的方法可以实现这一点。第一种方法是将数据和互斥量简单地包装在一个结构中,这样很明显它们属于一起。第二种方法是用简单的注释记录下来。这也迫使你在脑海中制定计划,这是一件好事。特别是,您不太可能共享数据但没有互斥量或者您有两个互斥量!您在网上找到的代码的作者都没有采用这两种非常简单的策略,这是反对它的另一个论据。
最后,示例代码忽略了您也可以 return 来自线程的内容,就像您 return 来自函数的内容一样。这可以通过 pthread_join()
的第二个参数来检索。使用 std::thread
,您可以获得更多选择。如果示例代码使用了该功能,它可以 returned 缓冲区填充文件中的数据。然后仅通过调用 pthread_join()
.
备注:
- 请注意,在 C++ 中,您还可以在名称 space 中包含一个非全局变量,或者在 class 中包含一个静态变量,但这类似于手头问题的全局变量.
- 除了互斥之外,还有一些原子操作有时可以用来替代互斥。请记住这一点作为另一件要学习的东西。
- 避免竞争资源的线程。如果他们这样做,他们往往会轮流阻止对方的进展,因此您既无法获得独立执行的好处,也无法获得并行执行加速的好处。更喜欢合作的线程。
- 如果可以,请避免共享数据。相反,更喜欢独占所有权。您可以设置一个任务并将其交给线程执行。完成后,线程会为您提供结果。特别是
std::unique_ptr
适合这种策略。由于数据不是共享的而是传递的,因此它不需要任何访问同步及其隐含的开销。这也更多的是上面所说的合作 - C++ 允许你做很多聪明的事情。例如,您可以包装一个数据结构和一个互斥锁,并且只允许在锁定互斥锁的情况下进行访问。查看智能指针实现及其
operator->
重载以获得灵感。 - 你画的class有构造函数和析构函数,但不要忘记删除复制构造函数和赋值运算符。查看 "Law of Three".
- 您使用
struct data_for_thread_t
的方法很接近,您只需要删除指针的间接访问。通常,避免在 C++ 中使用指针。 - 作为建议,请尝试正确实现示例(在后台读取文件)。运行后,请在 codereview.stackexchange.com 提交代码以供审核。不过,请务必先查看他们的指南,因为它们与 Stack Overflow 的指南大不相同。