1. _Thread_local
_Thread_local 可以将指定变量当做线程的内部变量,避免线程之间的变量共享。以 12-3 节的 count++ 为例,若 count 变量是每个线程内部的变量,则不会出现线程安全问题。故将代码修改如下:
#include <stdio.h>
#include <tinycthread.h>
// 创建线程生命周期的变量 main_count,线程结束时自动释放
_Thread_local int main_count = 0;
int Counter(int *count){
for (int i = 0; i < 100000; ++i) {
main_count += *count;
}
printf("main_count: %d\n", main_count);
return 0;
}
void TestThread(){
thrd_t t1;
thrd_t t2;
int count_t1 = 1; // t1 线程加 10w 次的 1
int count_t2 = 2; // t2 线程加 10w 次的 2
thrd_create(&t1, Counter, &count_t1);
thrd_create(&t2, Counter, &count_t2);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
printf("main_count: %d\n", main_count);
}
注意,每个线程都会有 main_count 变量,原因在于 _Thread_local 修饰的变量在每个线程创建之初就会被同时创建。其中,主线程的 main_count 变量没有做任何操作,子线程 t1 的 main_count 变量增加了 10w 次的 1,而子线程 t2 的 main_count 变量增加了 10w 次的 2。故程序执行结果为:
2. tss_t
使用 tss_t 及相关函数 tss_set(),tss_create(),tss_delete(),将指定内存绑定为线程内部内存,此时内存对应的变量即为线程内部变量。用此方法同样可以解决 12-3 小节的 count++ 问题。
整体逻辑如下:利用 tss_t 定义一个 key,再使用 tss_create() 函数创建 key(类比于锁的定义和初始化)。而后开辟一块内存,利用 tss_set() 函数将 key 与该块内存绑定,此时该内存为线程内部内存。该内存对应的变量即为线程内部变量,可任意操作。待操作完成后,使用销毁函数将该内存进行销毁,而后使用 tss_delete() 函数将 key 销毁。
使用 tss_t 后,每个线程都会有自己的 key ,内部内存和内部变量,不再使用共享资源。
#include <stdio.h>
#include <tinycthread.h>
#include <stdlib.h>
tss_t count_key; // 定义线程内部的一个key
int Counter(int *count){
int *main_count = malloc(sizeof(int)); // 开辟一块内存
*main_count = 0;
// 将内存与线程内部的 key 进行绑定,此时内存为线程内部的内存
if(tss_set(count_key, main_count) == thrd_success){
for (int i = 0; i < 100000; ++i) {
*main_count += *count; // 每个线程都有自己的 main_count 变量和 *main_count
}
}
printf("*main_count: %d\n", *main_count);
printf("*((int *)tss_get(count_key)): %d\n", *((int *)tss_get(count_key)));
}
// 释放 ptr 指针指向的内存
void MyFree(void *ptr){
printf("free prt:%#x\n", ptr);
free(ptr);
}
void TestTss(){
// 创建一个 key, 并传入将 key 对应内存销毁的函数 MyFree
if(tss_create(&count_key, MyFree) == thrd_success){
thrd_t t1;
thrd_t t2;
int count_t1 = 1;
int count_t2 = 2;
thrd_create(&t1, Counter, &count_t1);
thrd_create(&t2, Counter, &count_t2);
thrd_join(t1, NULL);
thrd_join(t2, NULL);
puts("t1,t2 ended.");
// 此时线程停止,key指向的内存被释放,下一步删除key
tss_delete(count_key);
// 主线程的 main_count
printf("*((int *)tss_get(count_key)): %d\n", *((int *)tss_get(count_key)));
}else{
perror("result");
}
}
每个线程都会有自己的 main_count 指针以及对应的变量值,不再出现线程安全问题。
遗憾的是,由于源码中 tss_create() 函数有一个逻辑目前无法解决,故最终程序执行结果有误。tss_create() 函数接收两个参数,第一个是 key 的地址,第二个是 key 对应内存的销毁函数,而源码中需要销毁函数为 NULL 时才能返回 thrd_success,不为 NULL 时返回 thrd_error。
此时逻辑出现 bug。不传入销毁函数,dtor == NULL,可以成功创建 key,但无法销毁 key 对应内存;传入销毁函数,dtor != NULL,可以销毁内存,但无法成功创建 key。此问题目前待定。