1 .使用线程局部存储的理由
   当我们希望这个进程的全局变量变为线程私有时,而不是所有线程共享的,也就是每个线程拥有一份副本时,这时候就可以用到线程局部存储(TLS,Thread Local Storage)这个机制了。

2.动态TLS
(1)调用TlsAlloc函数
    两种方式:

1>全局调用一次:   global_dwTLSindex=TLSAlloc();

如果程序只调用一次TlsAlloc,而不是每个线程都调用一次TlsAlloc()函数的话,多个线程就使用同一个索引值,不同线程虽然看起来用的是同名的TLS数组索引变量,但实际上各个线程得到的可能是                  不同DWORD值。其意义在于,每个使用TLS的线程获得了一个DWORD类型的线程局部静态变量作为TLS数组的索引变量。

2>进程内的每个线程都调用一次:

   每个线程得到不一样的索引值,调试可以看到每一次调用,索引值的大小是自加1而递增的。

函数流程:

  1>该函数会检索系统进程中的位标志并找到一个FREE标志,然后将该标志从FREE改为INUSE,并返回该标志在位数组中的索引,通常将该索引保存在一个全局变量中,因为这个值会在整个进程范围内(而不是线程范围内)使用。
  2>如果TlsAlloc无法在列表中找到一个FREE标志,会返回TLS_OUT_OF_INDEXES。
  3>TlsAlloc函数在函数返回之前,会遍历进程中的每个线程,并根据新分配的索引,在每个线程的Tls数组中把对应的元素设为0。

(2)调用TlsSetValue(dwTlsIndex,pvTlsValue)

将一个值放到线程的数组中
   该函数把pvTlsValue所标志的一个PVOID值放到线程的数组中,dwTlsIndex指定了在数组中的具体位置(由TlsAlloc得到)
   当一个线程调用TlsSetValue的时候,会修改自己的数组。但它无法修改另一个线程的TLS数组中的值。
(3)调用PVOID TlsGetValue(dwTlsIndex)

从线程的数组中取回一个值
    与TlsSetValue相似,TlsGetValue中会查看属于调用线程的数组
(4)调用TlsFree(dwTlsIndex)

   释放己经预订的TLS元素
   该函数会将进程内的位标志数组对应的INUSE标志重设回FREE
   同时该函数还会将所有线程中该元素的内容设为0。
   试图释放一个尚未分配的TLS元素将导致错误

// DynamicTLS.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <windows.h>
#include <process.h> #include <ctime>
#include <cstdlib>
#include <vector>
#include <iostream> using namespace std; clock_t gc_begin = 0;
clock_t gc_End = 0;
clock_t gc_Interval = 0; //全局 //unsigned long __stdcall threadProc(void *arg); CreateThread
unsigned int __stdcall ThreadProc(void *arg); int main(int argc, char *argv[])
{
/*
不同线程虽然看起来用的是同名的TLS数组索引变量,但实际上各个线程得到的可能是不同DWORD值。
其意义在于,每个使用TLS的线程获得了一个DWORD类型的线程局部静态变量作为TLS数组的索引变量。
*/ //这里使用 TlsAlloca() 可以,在线程内部调用这个函数也可以,使得每个线程都有一个不同的索引值,
//仅仅在这里调用一次,程序的实现也成功了,不同线程使用了同一个索引值也成功了 //DWORD tlsIndex = TlsAlloc(); //此步之后,当前线程实际上访问的是这个TLS数组索引变量的线程内的拷贝版本
//这里调用 TlsAlloc()产生的第一个索引值也是1
DWORD tlsIndex = 0;
vector<HANDLE> threads;
for (int i = 0; i < 2; ++i) {
HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ThreadProc,
(void*)tlsIndex, 0, NULL);
/*
使用_beginthreadex 和 CreateThread 创建线程产生的第一个索引值TlsAlloc()不同,
CreateThread是 1,_beginthreadex产生的是3,占用了两个索引位?
*/
//HANDLE h = (HANDLE)CreateThread(NULL, 0, ThreadProc,
//(void*)tlsIndex, 0, NULL);
Sleep(2000);
threads.push_back(h);
} for (size_t i = 0; i < threads.size(); ++i) {
WaitForSingleObject(threads[i], INFINITE);
CloseHandle(threads[i]);
}
}
// unsigned int __stdcall ThreadProc(void *arg)
{
DWORD tlsIndex = TlsAlloc();
//DWORD tlsIndex = reinterpret_cast<DWORD>(arg);
gc_begin = clock(); //从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数
TlsSetValue(tlsIndex, PVOID(gc_begin)); // 利用TlsSetValue 设置 值 printf("Thread ID: %d, Thread Begin Time: %d\n", GetCurrentThreadId(), gc_begin); Sleep(2000); gc_End = clock();
gc_Interval = gc_End - reinterpret_cast<clock_t>(TlsGetValue(tlsIndex));
double sec = 1.0 * gc_Interval / CLOCKS_PER_SEC; // 利用TlsGetValue取得值 printf("Thread ID: %d, Thread End Time: %d, Survival Time: %f\n", GetCurrentThreadId(), gc_End, sec); return 0;
}

  

  这里补充一下_beginthreadex()函数和CreateThread函数在创建线程时候,所创建的线程,调用TlsAlloc函数产生的索引值的不同,_beginthreadex()函数下的线程第一个索引值是3,而CreateThread函数下的线程第一个索引值是1,应该是_beginthreadex()函数下的线程出于某种原因占用了两位:

CreateThread函数下的线程第一个索引值:

_beginthreadex()函数下的线程第一个索引值:

3.静态TLS
(1)静态TLS变量的声明
  __declspec(thread) int number; 
(2)静态TLS的实现原理
  对于Windows系统来说,正常情况下一个全局变量或静态变量会被放到".data"或".bss"段中,但当我们使用__declspec(thread)定义一个线程私有变量的时候,编译器会把这些变量放到PE文件的".tls"段中。

  当系统启动一个新的线程时,它会从进程的堆中分配一块足够大小的空间,然后把".tls"段中的内容复制到这块空间中,于是每个线程都有自己独立的一个".tls"副本。所以对于用__declspec(thread)定义的同一个变量,它们在不同线程中的地址都是不一样的。

  线程环境块(TEB,Thread Environment Block)。这个结构里面保存的是线程的堆栈地址、线程ID等相关信息,其中有一个域是一个TLS数组,它在TEB中的偏移是0x2C。对于每个线程来说,x86的FS段寄存器所指的段就是该线程的TEB,于是要得到一个线程的TLS数组的地址就可以通过FS:[0x2C]访问到。

// Static_TLS.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include <windows.h>
#include <iostream> // 定义静态TLS全局变量
__declspec(thread) int __TlsValue = 0;
using namespace std;
DWORD WINAPI ThreadProcedure(LPVOID ParameterData);
int main()
{ // 设置主线程静态TLS的value为5
__TlsValue = 5;
HANDLE ThreadHandle = CreateThread(NULL, 0, ThreadProcedure, NULL, 0, NULL);
if (ThreadHandle)
{
// 等待直到子线程结束
WaitForSingleObject(ThreadHandle, INFINITE);
// 取得主线程静态TLS的值
cout << "主线程 __TlsValue=" << __TlsValue << endl;
}
return 0;
}
DWORD WINAPI ThreadProcedure(LPVOID ParameterData)
{
// 设置子线程value为10,并不影响主线程
__TlsValue = 10;
// 取得子线程静态TLS的值
cout << "子线程 __TlsValue=" << __TlsValue << endl;
return 0;
}

  

线程局部存储TLS的更多相关文章

  1. 【windows核心编程】线程局部存储TLS

    线程局部存储TLS, Thread Local Storage TLS是C/C++运行库的一部分,而非操作系统的一部分. 分为动态TSL 和 静态TLS 一.动态TLS 应用程序通过调用一组4个函数来 ...

  2. 线程局部存储 TLS

    C/C++运行库提供了TLS(线程局部存储),在多线程还未产生时,可以将数据与正在执行的线程关联.strtok()函数就是一个很好的例子.与它一起的还有strtok_s(),_tcstok_s()等等 ...

  3. 线程局部存储tls的使用

    线程局部存储(Thread Local Storage,TLS)主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护.. 因此也没有多线程间资源竞争问题 ...

  4. 线程局部存储TLS(thread local storage)

    同一全局变量或者静态变量每个线程访问的是同一变量,多个线程同时访存同一全局变量或者静态变量时会导致冲突,尤其是多个线程同时需要修改这一变量时,通过TLS机制,为每一个使用该全局变量的线程都提供一个变量 ...

  5. linux中的线程局部存储(TLS)

    http://blog.csdn.net/cywosp/article/details/26469435

  6. PE格式第八讲,TLS表(线程局部存储)

    PE格式第八讲,TLS表(线程局部存储) 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶复习线程相关知识 首先讲解 ...

  7. 线程局部存储(TLS)

    线程局部存储(TLS) 2011-10-11 09:59:28|  分类: Win32---API |  标签:tls   |举报 |字号 订阅   什么是线程局部存储 众所周知,线程是执行的单元,同 ...

  8. 【C# 线程】线程局部存储(TLS)理论部分 ThreadStatic|LocalDataStoreSlot|ThreadLocal<T>

    线程本地存储(TLS:Thread Local Storage) 线程本地存储(Thread Local Storage),字面意思就是专属某个线程的存储空间.变量大体上分为全局变量和局部变量,一个进 ...

  9. 基于TLS(线程局部存储)的高效timelog实现

    什么是timelog? 我们在分析程序性能的时候,会加入的一些logging信息记录每一部分的时间信息 timelog模块的功能就是提供统一的接口来允许添加和保存logging 我们正在用的timel ...

随机推荐

  1. 【传输对象】kafka传递实体类消息

    工具类 负责对象字节数组的相互转换,传输数据用 package com.yq.utils; import java.io.ByteArrayInputStream; import java.io.By ...

  2. C#下载歌词文件

    前段时间写了一篇c#解析Lrc歌词文件,对lrc文件进行解析,支持多个时间段合并.本文借下载歌词文件来探讨一下同步和异步方法. Lrc文件在网络上随处可见,我们可以通过一些方法获取,最简单的就是别人的 ...

  3. Windows下搭建FTP服务器

    一.什么是ftp? FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”.用于Internet上的控制文件的双向传输.同时,它也是一个应用程序(A ...

  4. UVa 1614 奇怪的股市

    https://vjudge.net/problem/UVA-1614 题意:输入一个长度为n的序列a,满足1<=ai<=i,要求确定每个数的正负号,使得所有数的总和为0. 思路:贪心部分 ...

  5. 【NOI2013】树的计数

    Description 我们知道一棵有根树可以进行深度优先遍历(DFS)以及广度优先遍历(BFS)来生成这棵树的DFS序以及BFS序.两棵不同的树的DFS序有可能相同,并且它们的BFS序也有可能相同, ...

  6. 《剑指offer》第十三题(机器人的运动范围)

    // 面试题:机器人的运动范围 // 题目:地上有一个m行n列的方格.一个机器人从坐标(0, 0)的格子开始移动,它 // 每一次可以向左.右.上.下移动一格,但不能进入行坐标和列坐标的数位之和 // ...

  7. MySQL函数GROUP_CONCAT() 实现多条数据合并

    group_concat()会计算哪些行属于同一组,将属于同一组的列显示出来,group by指定的列进行分组. 例如: -- 根据物流订单id查询所有物流订单,车源订单,车辆信息(多条数据合并为一条 ...

  8. WebView 实现JS效果和a标签的点击事件

    目前很多android app都可以显示web页面的界面,嵌入式开发,这个界面一般都是WebView这个控件加载出来的,学习该控件可以为你的app开发提升扩展性. 先说下WebView的一些优点: 可 ...

  9. android----AsyncHttpClient的get,post和图片上传

    async-http-client库是一个基于回调函数的Http异步通信客户端Android组件,是在Apache的HttpClient库的基础上开发构建而成的. Eclipse使用:导入androi ...

  10. android--------自定义控件ListView实现下拉刷新和上拉加载

    开发项目过程中基本都会用到listView的下拉刷新和上滑加载更多,为了方便重写的ListView来实现下拉刷新,同时添加了上拉自动加载更多的功能. Android下拉刷新可以分为两种情况: 1.获取 ...