1

我正在研究一个旧的遗留 ANSI C 系统,其中散布着很多全局变量。我是重构现有代码库的团队的一员,以使代码可重入和线程安全。我在这里找到了关于编写线程安全和可重入 ANSI C 代码的有用材料

根据我(诚然不完美)的理解,我提出了一个关于如何进行的建议——但我已经提出了一些需要解决的问题,并决定最好来这里找出最好的方法在实际开始编码之前进行设计。

/******************************************************************/
/*                                                                */
/* CURRENT situaton:                                              */
/* Global variables scattered accross a multitude of source files */
/*                                                                */
/******************************************************************/
static struct myStruct some_struct;
static long instance_counter;
static char[MAX_STR_LEN] instance_name;
static dbConnection * db_conn;
static SharedMemoryPtr * shrmem_ptr;




/*******************************************************************/
/*                                                                 */
/* PROPOSED solution:                                              */
/* Wrap all global variables into one thread local struct and      */
/* provide getter/setter* funcs for the variables (of course, this */
/* means that I will have to modify existing code to use the       */
/* context variable and the getter/setter methods instead of       */
/* directly accessing the global variables)                        */
/*                                                                 */ 
/*******************************************************************/

/* Thread local variables stored in ONE source file */

struct myStruct some_struct;
long instance_counter;
char[MAX_STR_LEN] instance_name;
dbConnection * db_conn;
SharedMemoryPtr * shrmem_ptr;


/* Thread local variable that provides getter/setter funcs to 'globals' */

typedef struct _ctx
{
    /* Getter functions */
    variable1_type (getter_func1*)(void);
    variable2_type (getter_func2*)(void);

    /* Setter functions */
    void (setter_func1*)(variable1_type v1);
    void (setter_func2*)(variable2_type v2);
} Context;

我有三个主要问题:

  1. 我采取的方法是好的(即最好的还是更好的方法之一)?如果没有,是否有更好的推荐(即“最佳实践”)方式来做我想做的事情?

  2. 如果在一个线程中更改了线程局部变量,该更改是否会反映在其他线程中?如果答案是否定的(我怀疑是这样),那么这意味着两个线程可以使用不同的变量值运行。在我能想到的几乎所有应用程序中,这是不可接受的——那么现有的多线程应用程序如何避免这种情况呢?我的基本理解告诉我,在写入之前必须获取锁,并且当有任何线程读取时,写入线程必须阻塞。如果不是这种情况,那么我将不胜感激澄清正确的事件顺序。我也很欣赏一些伪代码,这些伪代码将展示如何使用我的示例数据结构来实现这个读/写操作。

  3. 出于显而易见的原因,我在上下文结构中编写的 getter 函数伪代码理想情况下应该返回指针(以避免在每次检索时复制潜在的巨大数据结构)。但是,根据我之前在问题中提到的 IBM 页面(提供的链接):

可重入函数不会在连续调用中保存静态数据,也不会返回指向静态数据的指针。所有数据都由函数的调用者提供。

所以(据我了解),getter 函数不能返回指向静态数据的指针(除非我弄错了)。有人可以澄清一下。此外,如果我不应该从 gettter 函数返回指针,有什么方法/技术可以用来防止/避免返回数据的副本(正如我所说,一些结构非常庞大/沉重)。

[[在旁边]]

我正在 Ubuntu 12.04 LTS 上开发,所以我对 POSIX 兼容的解决方案很感兴趣。

4

1 回答 1

0

对于每个变量,您应该问的第一个问题是:它们是吗?

  • 只读(初始化一次)
  • 同步读/写(所有线程总是需要一致的副本)
  • 非同步读/写(每个线程都需要该变量,但不需要一致的副本或对私有副本感到满意)

探查器或调试器将能够告诉您您拥有什么类型。

如果所有变量都属于第三种类型,那么您的方法可以正常工作,您自己已经提到过这可能不是真的。

对于第一种类型(只读),只要初始化发生在并行代码之前,就可以安全地保留它们。

对于第三种类型,只需将__thread修饰符放在它前面,并确保它在并行部分中初始化。

第二种是最难的。您将需要使用某种同步来访问它:基本上一次只允许一个线程访问它。

有3个主要模型可以做到这一点:

  • 单锁。每次访问都必须先获取锁,使用变量,释放锁。看看pthread_mutex_*功能。此外,您还必须决定单个锁保护多少个变量,它可以像一个一直到大型结构或数组一样细粒度。
  • Readers/writers lock - 如果您允许多个并发读取访问,但一次只能进行一次写入访问,这是更好的选择。有一个pthread_mutex_*变体可以做到这一点。
  • 最后是原子 - 这些是特殊的硬件指令,能够一次修改最多同步的机器字。gcc将为您提供原子内置函数

希望这个对你有帮助。

于 2013-11-03T12:39:31.887 回答