线程的同步与互斥

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;

二、线程分离

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
pthread_detach(pthread_self());
joinable 和分离是冲突的,一个线程不能既是 joinable 又是分离的。
主线程分离
  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));
    }
其它线程自己分离
注:进程分离后,必须保证进程最后退出。否则分离后,进程join时不再阻塞等待,进程结束,进程退出,所有线程都会退出,该做的任务就没有完成。
主线程调用pthread_exit只是退出主线程,并不会导致进程的退出
是否被分离,是看线程tcb中自身的属性是joinable还是分离的(0还是1)

三、线程互斥

进程线程间的互斥相关背景概念
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
互斥量mutex
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。
多个线程并发的操作共享变量,会带来一些问题。

多线程并发抢票:

// -----------多线程并发抢票,互斥
#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--的操作不是原子性的,而是分为三个步骤:

  1. 将共享变量tickets从内存加载到寄存器中
  2. 更新寄存器里面的值 执行-1操作
  3. 将新值从寄存器写回共享变量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可能已经被其它线程修改了。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2023-12-07 04:06:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-07 04:06:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-07 04:06:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-07 04:06:04       18 阅读

热门阅读

  1. centos用户相关命令

    2023-12-07 04:06:04       30 阅读
  2. springboot工作原理

    2023-12-07 04:06:04       30 阅读
  3. 联合体union

    2023-12-07 04:06:04       36 阅读
  4. Oracle官网 账号及密码 -- 笔记

    2023-12-07 04:06:04       39 阅读
  5. 将元胞添加到元胞数组

    2023-12-07 04:06:04       34 阅读
  6. PTA 6-143 密码转换

    2023-12-07 04:06:04       37 阅读
  7. 2023亚太地区五岳杯量子计算挑战赛

    2023-12-07 04:06:04       41 阅读
  8. 前端面试题【构建工具篇】

    2023-12-07 04:06:04       29 阅读
  9. ROS 欧拉角

    2023-12-07 04:06:04       35 阅读
  10. FAQ:Constructors篇

    2023-12-07 04:06:04       28 阅读
  11. HG/T 5367.2-2022 轨道交通车辆耐电弧绝缘涂料检测

    2023-12-07 04:06:04       28 阅读
  12. rocketMQ-发送消息

    2023-12-07 04:06:04       30 阅读
  13. 获取图像大小 - 编程指南

    2023-12-07 04:06:04       37 阅读
  14. MongoDB导入导出命令

    2023-12-07 04:06:04       31 阅读