谈mutex的使用和线程安全、可重入




mutex用于多线程或者多进程之间对临界资源的访问互斥,在具体应用中,一般倾向于,一遇到资源竞争情况,就考虑用mutex,可是,mutex会大大降低程序的并发。因此,应该慎重: 

第一,避免用mutex,例如可以改用单线程程序

第二,迫不得已,尽可能使mutex的临界区短小,也就是说,尽可能晚lock,早unlock,减小锁的粒度

第三,可以考虑读写锁


本文来自:http://hi.baidu.com/yenoeepfqabiyzq/item/f2e46cdd276f74cb1b72b447


转自:http://hi.baidu.com/yenoeepfqabiyzq/item/5d0cc4d65f9d8357d73aae34

编写可重入和线程安全的代码在单线程的进程中,有且仅有一个控制流。这种进程执行的代码不必是可重入的,或线程安全的。在多线程的程序中,同一个函数或是同一个资源可能被多个控制流并发地访问。为了保证资源的完整性,为多线程程序写的代码必须是可重入的和线程安全的。本节提供了一些编写可重入和线程安全的代码的信息。
理解可重入和线程安全可重入和线程安全都是指函数处理资源的方式。可重入和线程安全是两个相互独立的概念,一个函数可以仅是可重入的,可以仅是线程安全的,可以两者都是,可以两者都不是。
可重入一个可重入的函数不能为后续的调用保持静态(或全局)数据,也不能返回指向静态(或全局)数据的指针。函数中用到的所有的数据,都应该由该函数的调用者提供(不包括栈上的局部数据)。一个可重入的函数不能调用不可重入的函数。一个不可重入的函数经常可以(但不总是可以)通过它的外部接口和功能识别出来。例如strtok是不可重入的,因为它保存着将被识别为token的字符串。ctime也不是一个可重入的函数,它会返回一个指向静态数据的指针,每次调用都可能覆盖这些数据。 线程安全一个线程安全的函数通过“锁”来保护共享的资源不被并发地访问。“线程安全”仅关心函数的实现,而不影响它的外部接口。 
在C中,局部变量是在栈上动态分配的,因此,任何一个不使用静态数据和其它共享资源的函数就是最平凡的线程安全的。例如,下面这个函数就是线程安全的:
/* thread-safe function */
int diff(int x, int y) {
int delta;
delta = y - x;
if (delta < 0)
delta = -delta;
return delta;
}
对全局变量的使用是线程“不安全”的。应该为每一个线程维护一份拷贝,或者将其封装起来,使得对它的访问变成串行的。 使一个函数变成可重入的在大部分情况下,不可重入的函数修改成可重入的函数时,需要修改函数的对外接口。不可重入的函数不能被多线程的程序调用。一个不可重入的函数,基本上不可能是线程安全的。 返回值很多不可重入的函数返回一个指向静态数据的指针。这个问题可以有两种解决办法:    1、返回从堆中分配的空间的地址。在这种情况下,调用者必须负责释放堆中的空间。这种办法的优点是不必修改函数的外部接口,但是不能向后兼容。现存的单线程的程序使用修改后的函数会导致内存泄露(因为它们没有释放空间)。    
2、由调用者提供空间。尽管函数的外部接口需要改变,仍然推荐这种方法。 
例如,一个strtoupper函数(个人感觉这个东东写得很儍)一个字符串转换成大写,实现为:
/* non-reentrant function */
char *srttoupper(char *string) {
static char buffer[MAX_STR_SIZE];
int index;
for (index = 0; string[index]; ++index) {
buffer[index] = toupper(string[index]);
}
buffer[index] = 0;
return buffer;
}


该函数既不是可重入的,也不是线程安全的。使用第一种方法将其改写为可重入的:
/* reentrant function (a poor solution)*/
char *strtoupper(char *string) {
char *buffer;
int index;
buffer = malloc(MAX_STR_SIZE); /*error check should be checked*/
for (index = 0; string[index]; ++index) { 
buffer[index] = toupper(string[index]);
}
buffer[index] = 0;
return buffer;
}
使用第二种方法解决:
/* reentrant function (a better solution)*/
char *strtoupper_r(char *in_str, char *out_string) {
int index;
for (index = 0; in_str[index]; ++index) {
out_str[index] = toupper(in_str[index]);
}
out_str[index] = 0;
return out_str;
}
为后继的调用保持数据一个可重入的函数不应该为后续的调用保持数据(即后继的调用和本次调用无关),因为下一次调用可能是由不同的线程调用的。如果一个函数需要在连续的调用之间维护一些数据,例如一个工作缓冲区或是一个指针,这些数据(资源)应该由调用这个函数的函数提供。例如:返回指定字符串中的下一个小写字符的函数。字符串只有在第一次调用时被提供,就像strtok一样。到达末尾时返回0(我认为这个函数写得有问题,姑且明白大意就好):
/* non-reentrant function */
char lowercase_c(char *string) {
static char *buffer;
static int index;
char c = 0;
if (string != NULL) {
buffer = string;
index = 0;
}
for ( ; c = buffer[index]; ++index) {
if (islower(c)) {
index++;
break;
}
}
return c;
}
该函数是不可重入的。要使它改写成可重入的,其中的静态数据应该由它的调用者维护。
/* reentrant function */
char reentrant_lowercase_c(char *string, int *p_index) {
char c = 0;
for ( ; c = string[*p_index]; ++(*p_index)) {
if (islower(c)) {
(*p_index)++;
break;
}
}
return c;
}
函数的对外接口和使用方法均改变了。 使一个函数变成线程安全的在一个多线程的程序中,所有的被多线程调用的函数多必须是线程安全的(或可重入的)。注意,不可重入的函数一般都是线程“不安全”的,然而,将它们改写成可重入的同时,一般就会将它们变成线程安全的。“锁”住共享资源使用静态数据或者其它任何共享资源(如文件、终端等)的函数,必须对这些资源加“锁”以实现对它们的串行访问,这样才能成为线程安全的函数。例如: 
/* thread-unsafe function */
int increament_counter() {
static int counter = 0;
counter++;
return counter;
}
/* pseudo-code thread-safe function*/
int increment_counter() {
static int counter = 0;
static lock_type counter_lock = LOCK_INITIALIZER;
lock(counter_lock);
counter++;
unlock(counter_lock);
return counter;
}
在一个使用线程库的多线程程序中,应该使用信号量来串行化共享资源的访问,或者其它“锁” 后面还有两节:A Workaround for Thread-Unsafe FunctionsReentrant and Thread-Safe Libraries与本文主题关系不大... 综上所述:可重入:多个线程调用是不会互相影响。例如第一个lowercase_c函数是不可重入的,在另一个线程中第二次调用它时,必将覆盖掉第一个线程中的设定的字符串。线程安全:解决多个线程共享资源的问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`fopen`是C语言标准库中的一个函数,用于在文件系统中打开一个文件,通常用于创建、读取或写入文件。在多线程环境中,函数的可重入性(reentrancy)是指一个函数可以在被多个调用者同时调用且保持内部状态一致的情况下正常运行的能力。 `fopen`本身并不是设计为直接支持多线程操作的,它是一个普通的C库函数,不是线程安全的。这意味着如果在多个线程中同时调用`fopen`,每个线程可能会看到不同的结果,比如可能覆盖其他线程正在处理的文件或打开同一个文件两次。这可能导致数据损坏或意外的行为。 在多线程环境下使用`fopen`,你应该确保以下几点: 1. **互斥访问**: 使用`mutex`或`pthread_mutex`等同步机制,确保对文件句柄的操作是互斥的,防止并发访问。 2. **线程局部存储**: 如果多个线程需要打开同一个文件,考虑使用线程局部变量存储文件描述符,这样每个线程都有一份独立的副本。 3. **正确关闭**: 避免在一个线程中打开文件后,其他线程也尝试打开同一文件。确保在所有线程完成操作后正确关闭文件,以释放系统资源。 4. **避免全局状态**: 尽量避免在函数内部使用全局变量,因为这可能导致不同线程之间的状态混淆。 尽管如此,`fopen`作为C库的一部分,它的可重入性并不意味着它可以直接在多线程环境中无锁使用。为了实现更高级别的线程安全,通常需要结合其他同步技术来确保操作的一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值