Linux中的线程与进程
一、线程栈
1、线程自己的栈空间
线程库会被加载到进程地址空间中(共享区),tid为线程对象的起始地址。
多线程情况下测试局部变量test_i
#define NUM 5
struct threadData
{
string threadname;
};
string toHex(pthread_t tid)
{
char buffer[128];
snprintf(buffer,sizeof(buffer),"0x%x",tid);
return buffer;
}
void* threadRoutine(void* args)
{
threadData* td = static_cast<threadData*>(args);
int cnt=10;
int test_i=0;
while(cnt--)
{
cout<<"pid:"<<getpid()<<" tid:"<<toHex(pthread_self())
<<" test_i:"<<test_i++
<<" &test_i:"<<&test_i
<<" threadName:"<<td->threadname<<endl;
sleep(1);
}
delete td;
return nullptr;
}
void InitData(threadData* td,int number)//面向过程
{
td->threadname = "thread-" + to_string(number);
}
int main()
{
vector<pthread_t> tids;
for(int i=0;i<NUM;++i)
{
pthread_t tid;
threadData* td = new threadData;
InitData(td,i);
pthread_create(&tid,nullptr,threadRoutine,td);//create线程的时候写入tid
tids.push_back(tid);
sleep(1);
}
for(int i=0;i<tids.size();++i)
{
pthread_join(tids[i],nullptr);
}
return 0;
}
每个线程的test_i都是独立的,有自己的地址,是在线程各自的栈空间上开辟的。
堆空间是共享的,每个线程分配一块。
int *p = nullptr;定义一个全局的p变量
主线程中可以获取子线程的栈区上的局部变量。
也就是说,线程之间虽然有独立的栈区,但线程之间也是可以做到互相访问的。(在地址空间中)
但实际使用时,规定不能这样使用。
2、线程局部存储
int g_val1=0;
__thread编译选项,运用线程局部存储原理,在共享区上创建一个私有全局变量
只能创建内置类型
应用:可以保存一些需要系统调用的值(获取一些基本属性),提高效率。
__thread int g_val2=0;
二、线程分离
int pthread_detach(pthread_t thread);
pthread_detach(pthread_self());
for (auto i : tids)
{
pthread_detach(i);//主线程分离
}
for (int i = 0; i < tids.size(); i++)
{
int n = pthread_join(tids[i], nullptr);
printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
}
![](https://img-blog.csdnimg.cn/direct/0ee04046e02342f495c63112468dcc47.png)
![](https://img-blog.csdnimg.cn/direct/2a21a795b1814ec1bf0d69e0e10a97b1.png)
![](https://img-blog.csdnimg.cn/direct/defdfd3f06e342feb14f86458ad50ee6.png)
主线程调用pthread_exit只是退出主线程,并不会导致进程的退出
三、线程互斥
多线程并发抢票:
// -----------多线程并发抢票,互斥
#define NUM 4
int tickets = 1000;
class threadData
{
public:
threadData(int number)
{
threadName = "thread-" + to_string(number);
}
public:
string threadName;
};
string toHex(pthread_t tid)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%x", tid);
return buffer;
}
void *getTicket(void *args)
{
threadData* td = static_cast<threadData*>(args);
while(tickets>0)
{
usleep(10000);
--tickets;
cout<<"threadname:"<<td->threadName<<" tid:"
<<toHex(pthread_self())<<" 剩余票数:"<<tickets<<endl;
}
cout<<"quit..."<<td->threadName<<endl;
return nullptr;
}
int main()
{
vector<pthread_t> tids;
vector<threadData*> thread_Datas;
for (int i = 1; i <= NUM; ++i)
{
pthread_t tid;
threadData* td = new threadData(i);
thread_Datas.push_back(td);
pthread_create(&tid, nullptr, getTicket, thread_Datas[i-1]);
tids.push_back(tid);
}
for(auto tid:tids)
{
pthread_join(tid,nullptr);
}
for(auto td:thread_Datas)
{
delete td;
}
return 0;
}
对一个全局变量多线程并发--
tickets--
的操作不是原子性的,而是分为三个步骤:
- 将共享变量tickets从内存加载到寄存器中
- 更新寄存器里面的值 执行-1操作
- 将新值从寄存器写回共享变量tickets的内存地址
例子:
假设现在有2个线程thread-1 和 thread-2进行抢票工作,分为上面的1,2,3步。
对于thread-1:执行第一步,从内存中读取到1000,1步完成。此时时间片到了,切换为thread-2
对于thread-2:时间片剩余较多,假设可以完整完成100次抢票工作。最后一次完成3步,此时eax中保存的值是900,最终将其写回内存中。然后切换回thread-1.
对于thread-1:继续第2步,先恢复上下文数据,将1000写到eax中,计算后为999,再进行第三步写回内存,此时内存中的值就变为999了,也就是多了100张票。
即对于thread2来说,tickets值前后不一致,即数据不一致问题。
tips:寄存器的内容!=寄存器
保存上下文到线程的对象内部,每次轮转到时恢复到CPU内的寄存器中
也就是thread-1认为自己一直在正确地--,实际上保存在上下文的那一份,拷贝回内存时,导致了最终的数据不一致问题。
数据不一致的原因:
tickets--的操作不是原子性的,即允许多个执行流同时进入,会互相干扰。
为避免该问题,则需要加锁操作。
为什么票数<=0时还能抢票呢?
判断tickets时,成立进入。
但设置了usleep,为了让多线程都停留在判断进入,但没有--操作,就被切换走了。
此时就会出现tickets为1,但有>1个线程判断成立。
后续的--操作就不判断tickets的值是否>0了
每次--都需要重新读取tickets的值,在这之前tickets可能已经被其它线程修改了。