做人呢,最紧要就系开心啦

Linux多线程3:线程安全和可重入

782次阅读
没有评论

1. 线程安全和可重入函数的重要性

安全访问竟态数据,涉及两个概念

线程安全:

不访问全局资源的函数;
或者访问全局资源,但是加 mutex 的函数;
比如常见的 malloc, free, printf 等函数,会访问全局资源,其内部都添加了锁保护,因此是线程安全的;

特点,多线程对竟态的资源访问,加锁即可;

信号安全:

不访问全局资源的函数;
访问全局资源,但是做保存和恢复的函数;
信号函数,属于线程内资源,不能加锁 (可能引起死锁);
信号函数打断线程后,信号函数会执行完,才会重新调度,因此只要对竟态资源添加保存和恢复,不会影响原线程数据;

特点: 不能用 mutex 等锁

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include <sys/types.h>

char *strtoupper(char *string)
{static char buffer[1000];
                int index;

                for (index = 0; string[index]; index++)
                                buffer[index] = toupper(string[index]);
                buffer[index] = 0; 

                return buffer;
}

void *thread_fun(void *param)
{while (1) {usleep(100);
                printf("%s\n", strtoupper((char *)param));
        }
}

int main(void)
{
        pthread_t tid1, tid2;
        int ret;

        printf("main pid:%d, tid:%lu\n", getpid(), pthread_self());

        ret = pthread_create(&tid1, NULL, thread_fun, "hello world");
        if (ret == -1) {perror("cannot create new thread");
                return 1;
        }

        ret = pthread_create(&tid2, NULL, thread_fun, "world hello");
        if (ret == -1) {perror("cannot create new thread");
                return 1;
        }

        if (pthread_join(tid1, NULL) != 0) {perror("call pthread_join function fail");
                return 1;
        }

        if (pthread_join(tid2, NULL) != 0) {perror("call pthread_join function fail");
                return 1;
        }

        return 0;
}

2. 非学究的概括,可重入函数要满足两个条件:

(1) 函数是线程安全的;
(2) 函数是可中断的 (对于 Linux, 异步的信号),执行了中断处理后,再回来继续执行函数,结果仍然正确;
线程安全和信号安全的案例
https://deadbeef.me/2017/09/reentrant-threadsafe

线程安全,信号不安全案例:
__thread int test = 123;

#include <threads.h>
// `t` is now local to each thread
thread_local int t;// 线程级全局变量,线程安全,但可以被信号访问
// 每个线程都会创建一个实例,线程退出,被释放;void swap(int *x, int *y) {
  t = *x;
  *x = *y;
  // my_func() could be called here
  *y = t;
}

void my_func() {
  int x = 1, y = 2;
  swap(&x, &y);
}

信号安全,线程不安全案例

int t;

// t 可以恢复 (信号函数执行完,才继续执行线程),void swap(int *x, int *y) {
  int s;
  // save global variable
  s = t;
  t = *x;
  *x = *y;

  // my_func() could be called here
  *y = t;
  // restore global variable
  t = s;
}

void my_func() {
  int x = 1, y = 2;
  swap(&x, &y);
}

#include <signal.h>
#include <stdio.h>
struct two_long {long a, b;} data;
void signal_handler(int signum){printf ("%d, %d\n", data.a, data.b);
        alarm (1);
}

int main (void){static struct two_long zeros = { 0, 0}, ones = {1, 1};
        signal (SIGALRM, signal_handler);
        data = zeros;
        alarm (1);
        while (1)
        {data = zeros; data = ones;}

Linux 多线程 3:线程安全和可重入

典型的程序设计满足下面条件之一的多数是不可重入函数:
(1) 使用了静态数据结构;
(2) 调用了 malloc 或 free; 申请的内存在共同的堆里;
(3) 调用了标准 I / O 函数; 标准 io 库很多实现都以不可重入的方式使用全局数据结构。
(4) 进行了浮点运算. 许多的处理器 / 编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现。)

3 非学究可重入与线程安全注意事项

如果一个函数中用到了全局或静态变量(const 除外),那么它不是线程安全的,也不是可重入的;

如果我们对它加以改进,在访问全局或静态变量时使用互斥量或信号量等方式加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题 (比如信号里调用加锁函数,可能出现死锁);

如果将函数中的全局或静态变量去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。

4 多线程情况下的信号处理

方法 1:替换掉不可重入函数

Signal_handler()
{Printf->write}

打印,但是不会涉及线程安全问题;

方法 2:将信号的异步问题,转为同步化

在主线程将信号屏蔽掉,则其他同组线程也会屏蔽掉对应信号;
然后专门创建一个子线程,重新设置该信号,并且用 sigwaitinfo 函数,通过同步等待的方式获取信号,然后做信号处理

#include <signal.h>
#include <stdio.h>
struct two_long {long a, b;} data;
void signal_handler(int signum){printf ("%d, %d\n", data.a, data.b);
        alarm (1);
}

int main (void){static struct two_long zeros = { 0, 0}, ones = {1, 1};
        signal (SIGALRM, signal_handler);
        data = zeros;
        alarm (1);
        while (1)
        {data = zeros; data = ones;}
}

leon@pc:$ cat signalsync.c

#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>

void sig_handler(int signum)
{
        static int j = 0;
        static int k = 0;
        pthread_t  sig_ppid = pthread_self();
        // used to show which thread the signal is handled in.

        if (signum == SIGUSR1) {printf("thread %d, receive SIGUSR1 No. %d\n", sig_ppid, j);
                j++;
                //SIGRTMIN should not be considered constants from userland,
                //there is compile error when use switch case
        } else if (signum == SIGRTMIN) {printf("thread %d, receive SIGRTMIN No. %d\n", sig_ppid, k);
                k++;
        }
}

void* worker_thread()
{pthread_t  ppid = pthread_self();
        pthread_detach(ppid);
        while (1) {printf("I'm thread %d, I'm alive\n", ppid);
                sleep(10);
        }
}

void* sigmgr_thread()
{
        sigset_t   waitset, oset;
        siginfo_t  info;
        int        rc;
        pthread_t  ppid = pthread_self();

        pthread_detach(ppid);

        sigemptyset(&waitset);
        sigaddset(&waitset, SIGRTMIN);
        sigaddset(&waitset, SIGUSR1);

        while (1)  {rc = sigwaitinfo(&waitset, &info);
                if (rc != -1) {printf("sigwaitinfo() fetch the signal - %d\n", rc);
                        sig_handler(info.si_signo);
                } else {printf("sigwaitinfo() returned err: %d; %s\n", errno, strerror(errno));
                }
        }
}

int main()
{
        sigset_t bset, oset;
        int             i;
        pid_t           pid = getpid();
        pthread_t       ppid;

        // Block SIGRTMIN and SIGUSR1 which will be handled in
        //dedicated thread sigmgr_thread()
        // Newly created threads will inherit the pthread mask from its creator
        sigemptyset(&bset);
        sigaddset(&bset, SIGRTMIN);
        sigaddset(&bset, SIGUSR1);
        //A new thread inherits a copy of its creator's signal mask.
        if (pthread_sigmask(SIG_BLOCK, &bset, &oset) != 0)
                printf("!! Set pthread mask failed\n");

        // Create the dedicated thread sigmgr_thread() which will handle
        // SIGUSR1 and SIGRTMIN synchronously
        pthread_create(&ppid, NULL, sigmgr_thread, NULL);

        // Create 5 worker threads, which will inherit the thread mask of
        // the creator main thread
        for (i = 0; i < 5; i++) {pthread_create(&ppid, NULL, worker_thread, NULL);
        }

        // send out 50 SIGUSR1 and SIGRTMIN signals
        for (i = 0; i < 50; i++) {kill(pid, SIGUSR1);
                printf("main thread, send SIGUSR1 No. %d\n", i);
                kill(pid, SIGRTMIN);
                printf("main thread, send SIGRTMIN No. %d\n", i);
                sleep(10);
        }
        exit (0);
}

4 典型的不安全的 Linux 库函数

Linux 多线程 3:线程安全和可重入

正文完
 
admin
版权声明:本站原创文章,由 admin 2022-01-13发表,共计4734字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)