多线程编程杂谈(上)

问题

线程执行的过程中可以强制退出吗?

主动退出?被动退出?

问题抽象示例

需要解决的问题

g_run 全局变量需要保护吗?

如何编码使得线程中每行代码的执行可被 g_run 控制?

线程代码在被 g_run 控制并 "强制退出" 时:

  • 如何指定线程的返回值?
  • 如何回收初始化时申请的资源?

关键:如何实现 g_run 的原子化操作

以下是原子操作吗?如果不是,有什么影响?

  • g_run = 1
  • if(g_run) {}
  • while(g_run) {}
  • g_run++, g_run--, g_run += n, ...

下面的程序会输出什么?为什么?

该程序创建100个线程,每个线程会对全局变量 g_var 进行 10000次加一操作,但由于 g_var 是全局变量,是临界资源,并且 g_var++ 操作不是原子操作,g_var++ 在汇编层面分为三个步骤,首先是将 g_var的值读到某个寄存器上,然后是在寄存器上对这个值加一,最后将加一后的值写入到 g_var,所以这个程序的运行结果的打印,g_var 的值会小于等于 1000000

现代 C 语言的原子化特性

_Atomic 是一个关键字,被修饰的变量具有原子化特性

_Atomic 是与 const 和 volatile 同类型的关键字 (可搭配 typedef 使用)

思考

_Atomic 是现代 C 语言中的关键字,如果不使用它,int 类型变量的操作还是原子的吗?

变量原子性测试

test1.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <memory.h>
#include <semaphore.h>

static _Atomic volatile int g_var = 0;

void* thread_func(void* arg)
{  
    int i = 0;
    
    for(i=0; i<10000; i++)
    {
        g_var++;
    }
    
    return NULL;
}


int main()
{
    pthread_t t[100] = {0};
    
    int i = 0;
    void* ret = NULL;
    
    for(i=0; i<100; i++)
    {
        pthread_create(&t[i], NULL, thread_func, NULL);
    }
    
    for(i=0; i<100; i++)
    {
        pthread_join(t[i], &ret);
    }
    
    printf("g_var = %d\n", g_var);
    
    return 0;
}

第 11 行,我们将 g_var 通过 _Atomic 关键字将它修饰为原子变量,对 g_var 的操作不可被打断

程序运行结果如下图所示:

由于g_var 通过 _Atomic 关键字将它修饰为原子变量,所以 g_var++ 变成了原子操作

从微观进行分析

将 C 代码转换为汇编代码可以看出,g_var = 1 和 if(g_var) 是原子操作,而 g_var++ 不是原子操作

结论

对于 int 型全局变量,赋值操作是原子操作 (g_var = 1)

基于值的条件判断是原子操作 (if(g_var))

递增 / 递减 操作不是原子操作 (++, --, +=, -=)

线程退出解决方案

({}) 是 gcc 中的语法,这种语法允许在一个表达式的上下文中包含多条语句,这样可以使得宏定义 R 具有返回值,返回值为 code

R 这个宏定义会判断 g_run,如果 g_run 为 0,则调用 pthread_exit() 来结束线程的执行,线程结束的返回值默认为 NULL,也可填在 R 中的第二个参数指定;非 0 则执行 code

注意事项

不是每一行代码都需要使用 R(...) 宏进行线程退出控制

建议编码时,精心设计线程退出的 关键点 及 返回值

实践中,通常以代码块为粒度进行线程退出控制

临界区代码一定不要 R(...) 宏进行线程退出控制 (可能造成死锁)

线程退出解决方案

test2.c

#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */
#define _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <memory.h>
#include <semaphore.h>

static volatile int g_run = 0;
static _Atomic volatile int g_var = 0;

#define R(code, ...)    ({                   \
    if( !g_run )                             \
        pthread_exit((NULL, ##__VA_ARGS__)); \
                                             \
    code;                                    \
})

void cleanup_handler(void* arg)
{
    printf("%s : %p\n", __FUNCTION__, arg);
    free(arg);
}

void* thread_func(void* arg)
{  
    int i = 0;
    char* pc = malloc(16);
    
    if( pc )
    {
        pthread_cleanup_push(cleanup_handler, pc);
        
        R(strcpy(pc, "Hello World!"), (void*)111);
        R(printf("%s\n", pc));
        
        for(i=0; i<10000; i++)
        {
            R(g_var++);
        }
        
        pthread_cleanup_pop(1);
    }
    
    return NULL;
}


int main()
{
    pthread_t t[100] = {0};
    
    int i = 0;
    void* ret = NULL;
    
    for(i=0; i<100; i++)
    {
        pthread_create(&t[i], NULL, thread_func, NULL);
    }
    
    for(i=0; i<100; i++)
    {
        pthread_join(t[i], &ret);
        if( ret )
        {
            printf("ret = %lld\n", (long long)ret);
        }
    }
    
    printf("g_var = %d\n", g_var);
    
    return 0;
}

第 34 行,我们通过 pthread_cleanup_push(...) 函数来注册线程退出时的资源清理函数,当 thread_func 线程退出时,会调用 cleanup_handler(...) 函数来清理资源,这里是用来释放 pc 所指向的堆空间

在线程可能会中途退出的代码片段中使用宏定义 R(...),但不宜每个地方都使用,这样会影响程序的执行效率

程序运行结果如下图所示:

由于 g_run 的值为 0,所以每个子线程执行到第 36 行,就会结束,并调用到资源清理函数 cleanup_handler,拿到的 pthread_exit(...) 的返回值也是 111

思考

是否存在其它中途退出线程的方法?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值