羊蹓狼 发表于 2024-8-23 15:22:37

[Linux#41][线程] 线程的特性 | 分离线程 | 并发的问题

1.线程的特性

进程和线程的关系如下图:

https://img-blog.csdnimg.cn/img_convert/420af40a4f121a49a6348535b975d28b.png
   关于进程线程的问题
• 怎样对待之前学习的单进程?具有一个线程执行流的进程
   线程 ID 及进程地址空间布局


[*]pthread_ create 函数会产生一个线程 ID,存放在第一个参数指向的地址中。 该线程 ID 和前面说的线程 ID 不是一回事。
[*] 前面讲的线程 ID 属于进程调理的范畴。因为线程是轻量级进程,是操纵系统 调理器的最小单元,以是需要一个数值来唯一表现该线程。
[*] pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即 为新创建线程的线程 ID,属于 NPTL 线程库的范畴。线程库的后续操纵,就是根据 该线程 ID 来操纵线程的。
[*] 线程库 NPTL 提供了 pthread_ self 函数,可以获得线程自身的 ID:pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于 Linux 目前实现的 NPTL 实现而 言,pthread_t 类型的线程 ID,本质就是一个进程地址空间上的一个地址。

https://img-blog.csdnimg.cn/img_convert/ab048135bfe8c5632852d663285e4f50.png
在线程栈中
   创建线程,怎样实现批量传参数据--vector+循环 创建
void *threadRoutine(void *args)//接收类指针
{
    threadData *td=static_cast<threadData *>(args);
    int i=0;
    while(i<10)
    {
      cout<<"pid: "<<getpid()<<" , ";
    }
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
      pthread_t tid;
      threadData *td=new threadData;
      pthread_create(&tid,nullptr,threadRoutine,td);
      tids.push_back(tid);

    }
} 留意:对于 threadData *td=new threadData;传入函数后要对 void * 强转
threadData *td=static_cast<threadData *>(args);

防止两个栈空间混乱
以是 要 传指针,指向的是堆空间,一线程一个
下面函数的初始化是怎样实现的呢, InitThreadData 的计划
struct threadData
{
    string threadname;
};

// __thread threadData td;

string toHex(pthread_t tid)
{
    char buffer;
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}

void InitThreadData(threadData *td, int number)
{
    td->threadname = "thread-" + to_string(number); // thread-0
}
snprintf 函数被用来将线程 ID (tid) 转换成一个十六进制字符串,并存储在 buffer 字符数组中
监测的打印:
while :; do ps ajx | head -1 && ps ajx | grep myprocess |grep -x grep;sleep 1;done
//将ps ajx替换为ps -aL 所有的线程,执行的都是这个函数?是的

   

[*]1. 但每个线程都有本身独立栈布局,打印测试
会每个线程的 test_i 有本身的地址空间,是独立的

https://img-blog.csdnimg.cn/img_convert/dc80d6b8d40819061c6ba580b4b1ff89.png

多执行流--可重入函数,线程和线程几乎没有秘密,固然有独立栈布局,要是想访问也是可以的
   

[*]2. 线程的栈上数据,也是可以被其他线程看到并访问的
线程可以拿到某一线程数据,例如对主线程进行测试,发现访问到了
https://img-blog.csdnimg.cn/img_convert/ba01a3ce8b25eadf87c1c8e626c2970f.png

   

[*]3. 全局变量可以被所有线程同时看到的,++测试:
cout << "pid: " << getpid() << ", tid : "
      //   << toHex(number) << ", threadname: " << td->threadname
      //         << ", g_val: " << g_val << " ,&g_val: " << &g_val <<endl;
https://img-blog.csdnimg.cn/img_convert/1bbc85fe0ad80360ceb84f030af5859b.png
g_val 数据称为共享资源
   假如我线程想要一个私有的全局变量呢??
__thread(编译选项创建) int g_val 线程的局部存储,每个线程都对共享资源存储了一份

https://img-blog.csdnimg.cn/img_convert/13652ca5220a4399dbe6b5b8a318bddc.png
界说出线程级别的全局变量,并且互不干扰,进行线程的局部存储!只能界说内置类型,可以用于线程函数
2. 分离线程

• 默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join操纵,否则无法开释资源,从而造成系统走漏。
• 假如不关心线程的返回值,join 是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动开释线程资源。int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程本因素离: pthread_detach(pthread_self());
joinable 和分离是冲突的,一个线程不能既是pthread_join又是 pthread_detach的。
//测试发现矛盾
// void *threadRoutine(void *args)
// {
//   pthread_detach(pthread_self());//自己分家

int main(){
    vector<pthread_t> tids;
    // for(auto i : tids)
    // {
    //   pthread_detach(i);//被父亲驱逐分家
    // }
    // cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl;

    for (int i = 0; i < tids.size(); i++)
    {
      int n = pthread_join(tids, nullptr);
      printf("n = %d, who = 0x%x, why: %s\n", n, tids, strerror(n));
    }   怎样创建多个进程
vector<pthread_t> tids;//循环创建
    for (int i = 0; i < NUM; i++)
    {
      pthread_t tid;
      threadData *td = new threadData;
      InitThreadData(td, i);

      pthread_create(&tid, nullptr, threadRoutine, td);
      tids.push_back(tid);
      //sleep(1);
    } 3.并发的问题

   进程线程间的互斥相关背景概念
• 临界资源:多线程执行流共享的资源就叫做临界资源
• 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
• 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源, 通常对临界资源起掩护作用
• 原子性(背面讨论怎样实现):不会被任何调理机制打断的操纵,该操纵只有 两态,要么完成,要么未完成
   互斥量 mutex
• 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间 内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
• 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通 过数据的共享,完成线程之间的交互。
• 多个线程并发的操纵共享变量,会带来一些问题。
   模仿实现抢电影票
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>

using namespace std;

#define NUM 4

class threadData
{
public:
    threadData(int number)
    {
      threadname = "thread-" + to_string(number);
    }

public:
    string threadname;
};

int tickets = 1000; // 用多线程,模拟一轮抢票

void *getTicket(void *args)
{
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->threadname.c_str();
    while (true)
    {
      if(tickets > 0)
      {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets); // ?
            tickets--;
      }//有票的时候才进行抢票
      else
            break;
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

int main()
{
    vector<pthread_t> tids//存储多线程的id信息
    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);
      tids.push_back(tid);
    }
    //四个线程同时运行抢票 留意:

[*]对象数组的创建和传入,传入类 类型,因为类具有可扩展性
vector<threadData *> thread_datas;
threadData *td = new threadData(i);//创建对象传入
thread_datas.push_back(td);//对象存储到数组空间中
pthread_create(&tid, nullptr, getTicket, thread_datas);//传入
2. 对于两个vector 采取处理
    for (auto thread : tids)
    {
      pthread_join(thread, nullptr);
    }

    for (auto td : thread_datas)
    {
      delete td;
    }
    return 0;
}
运行发现,存在抢票抢超了,共享数据-->数据不同等问题! 肯定和多线程并发访问是有关系的

https://i-blog.csdnimg.cn/direct/7c9c11c08d47494985cd97c2b61782d1.png
   起首需要来理解一下 tickets--   为什么不是一个原子操纵?


[*]--操纵并不是原子的,而是对应三条汇编指令,其中在执行任何一条指令时都有大概被切走


[*]

[*]load:将共享变量tickets从内存加载到寄存器中
[*]update:更新寄存器里面的值,执行-1操纵
[*]store:将新值,从寄存器写回共享变量ticket的内存地址

每一步都会对应一条汇编操纵

https://img-blog.csdnimg.cn/img_convert/08a5e3b243d63bcf25a27788f253b854.png
tickets--=>1.moveax 2.-- 3.mov eax
图解:

https://img-blog.csdnimg.cn/img_convert/dd1b2a2a627e7f2aaed2d601bd9d175d.jpeg
寄存器不等于寄存器的内容,线程在执行的时候,将共享数据,加载到 CPU 寄存器的本质:把数据的内容,变成了本身的上下文,同时本身拷贝了一份数据
拿走数据,拿走上下文,每次通过上下文轮番刷新

   对一个全局变量进行多线程并发--/++是否是安全的?(并发情况下,对变量的操纵)
不安全
出现负数缘故原由分析 sum
while (true)
    {
      if(tickets > 0)
      {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets); // ?
            tickets--;
      }//有票的时候才进行抢票

[*]if语句判断条件为真以后(判断也需要CPU参与),代码可以并发的切换到其他线程,"同一时刻"有多个线程判断tickets时tickets的值是雷同的
[*]usleep用于模仿漫长业务的过程,在这个漫长的业务过程中,大概有很多个线程会进入该代码段
[*]tickets-- 操纵本身就不是一个原子操纵

每个进程都认为本身是 1,操纵完第一步之后就被切走了,我在修改的时候你也修改了,例如:
假设我们有两个线程,它们都在实验递减全局变量 tickets 的值。假如没有适当的同步机制,大概会发生以下情形:

[*]线程 A 和 线程 B 都查抄 tickets 的值是否大于 0。
[*]线程 A 和 线程 B 都发现 tickets 的值大于 0,因此都会实验递减 tickets 的值。
[*]线程 A 先递减 tickets 的值,例如从 1000 减到 999。
[*]线程 B 也递减 tickets 的值,但是由于它读取的是原始值 1000,以是也会将 tickets 的值从 1000 减到 999。
[*]最终结果:尽管两个线程都执行了递减操纵,但 tickets 的值只淘汰了 1 而不是期望的 2。

https://img-blog.csdnimg.cn/img_convert/190f267a00d289b2266e3fd6d274283a.jpeg
这就是并发造成的

   怎么办理??
对共享数据的任何访问,保证任何时候只有一个执行流访问!---互斥,加锁,下篇文章中我们将详细讲解~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: [Linux#41][线程] 线程的特性 | 分离线程 | 并发的问题