------------------------------------------------------------
 author:     hjjdebug
 date:       2024年 03月 17日 星期日 17:04:47 CST
 descpriton: 线程的通俗解释
 ------------------------------------------------------------
目录:
1. 什么是线程? 
 2. 线程函数长得什么样? 
 3. 为什么要使用线程.
 4. 线程控制.
  4.0 线程的执行和挂起
  4.1: 线程中的互斥操作
  4.2: 线程中的同步操作
  4.3: 为什么不用全局变量作线程间控制变量?
  4.4: 全局变量在线程同步或互斥中没有立足只地吗?
  4.5: 线程的创建与销毁
  
--------------------
 1. 什么是线程? 
 --------------------
线程是对象吗? 线程不是对象, 对象是一个数据结构变量,对应一块数据内存.
 线程是函数吗? 线程不是普通意义下的函数, 但它也是函数.
 线程是代码吗? 线程是代码. 线程执行就是执行线程函数中的代码.
那线程函数与普通函数又有什么区别呢?
 线程函数形式上是void* thread_func(void *) 形式.
 当该函数被创建线程函数 pthread_create(&threadID, NULL, thread_func, NULL) 引用时, 它就成了线程函数.
 线程函数就是被cpu 要调度执行的函数.
通常意义下的函数往往是主线程下的函数, 我们经常书写这样的函数而对它没有感觉.
-----------------------
 2. 线程函数长得什么样? 
 -----------------------
 如下示例.
 void * thread_func(void *args)
 {
     while(1)
     {
         wait_signal();
         ....
         if(stop) break;
     }
     return NULL;
 }
线程函数通常都有一个while循环(或类似于while循环), 因为一旦它退出循环,就意味着线程的终结.
 -----------------------
 3. 为什么要使用线程.
 -----------------------
  因为用线程可以实现并发执行. 提高效率.
  同时还可以有其它一些用途.
  想一想, 原来是一个人干活, 现在能n个人干活, 是不是很兴奋呢!
  例如: 蚂蚁下载, 下载一个大文件,一只蚂蚁要下很长时间, n只蚂蚁把大文件分成n块协同下载,很快就下载完成了.
 -----------------------
  4. 线程控制.
 -----------------------
  一个线程相当于一个人,多个线程相当于多个人.
  一个人可以有条不紊的按先后次序干活, 多个人就必需要分工协作,否则就会乱套.
-----------------------
  4.0 线程的执行和挂起
 -----------------------
  当要执行一个线程时,一个重要的概念是它应该被执行还是应该休息. 
  当条件满足时,它应该被执行,
  当条件不满足时,它应该被挂起.
  用代码来表示:
  if(ready)  // 条件ready
  {
      running;  //线程被执行
  }
  else
  {
      sleep;   // 线程被挂起
  }
   
 -----------------------
 4.1: 线程中的互斥操作
 -----------------------
   其本质是只有一个线程在运行敏感代码,其余线程运行到此处都会被挂起.
   在线程控制中, 用全局变量有很多毛病(一会讲清), 而应该用操作系统原语. 就是说应该用操作系统给你提供的变量.
   其中之一是互斥变量mutex , 互斥变量主要是用来实现线程间的代码互斥.
   线程互斥量的典型用法:
   Mutex v_mutex; // 声明一个Mutex 变量 v_mutex
   v_mutex.lock() // 要进入敏感代码,先申请锁,申请到了我就执行, 申请不到就让我sleep.
   critical_code();  //保证这块代码仅有一个线程可以执行
   v_mutex.unlock(); //代码执行完要释放锁, 这样别的线程才可能得到资源来执行这块代码
-----------------------
 4.2: 线程中的同步操作
 -----------------------
   其本质还是线程的运行和挂起来完成. 得不到资源的被挂起实现执行顺序.
   线程的同步原语是sem_t; 
   其典型用法是:
   sem_t v_sem; //semphore 就是一个计数器.
   sem_init(&v_sem,0,1); //第2个参数0表示它不是进程共享,而是线程共享,第3个参数表示semphore的初始值
  sem_wait(&v_sem); //申请一个资源, 申请到了执行,申请不到资源会被挂起.
   sem_post(&v_sem); //释放一个资源
   申请资源相当于P操作,释放资源相当于V操作, 这就是典型的共享资源的PV操作.
  线程同步典型的例子就是生产者,消费者的例子. 生产者一定要在消费者的前面执行.
   同样,医生作手术次序也很重要, 
   第一步,把病人抬上手术台. 
   第二步,把手术刀放到医生手上.
   第三步,医生开始做手术.
次序错了或者缺了哪一步, 那医生还做个鬼啊!
----------------------------------------
 4.3: 为什么不用全局变量作线程间控制变量?
 ----------------------------------------
   说实话, 互斥量就是一个布尔值, 信号量就是一个整形值(给1也能起互斥量的作用), 用全局变量不可以替代吗?
   不可以,主要是因为对全局变量的操作不是原子操作, 所谓原子操作就是一口气干完, 从汇编的角度看,有时候甚至
   关闭中断, 专门干你那点活,然后再开中断. 总之就是保证不被打扰的意思.
   举个火车站售票的例子吧, 当你要满G158 石家庄到北京车票,车票还有一张, 此时另一个窗口也有一个顾客买G158车票石家庄到北京
   用全局变量
   if(tick_count>0)
   {
    tick_count--;
   }
  你这个线程看到tick_count 为1, 你付款了,当你把tick_count 变量拿起来,准备减1时,线程被切换走了.
  另一个窗口的线程也看到了tick_count=1, 所以它把票卖走了, 线程又切回到你这里,你继续执行减1,你也拿到了最后这张票.
  问题出在哪? 一张票卖给了2个人! 就在于这个tick_count--不是原子操作, 还没有干好呢,被切换走了.
  如果用set_t 就不会有这种事.
  sem_wait(sem_count); // 拿到了这个资源, 你就可以干你的事,拿不到,线程就被挂起来了,不会出现竞争.
 ------------------------------------------------
  4.4: 全局变量在线程同步或互斥中没有立足只地吗?
 ------------------------------------------------
  也不是, 它只能做简单的同步与互斥操作, 不会被线程同时操作到的那一种.
  例如: 象棋对弈,红先黑后,轮流行棋. 只有2个线程,
  初始化: red_flag=1, black_flag=0;
  红棋:
  while(1)
  {
      if(red_flag)
      {
        行棋;
        red_flag=0;   //之所以可以用全局变量,是因为对red_flag,black_flag 的操作
        black_flag=1; // 两个线程根本不可能同时操作到同一个变量. 因为红方在操作时,黑方在睡眠
      }                // 同理,黑方在操作者两个变量时,红方在睡眠,所以不会引起冲突!
      else
      {
       sleep(1);
      }
   }
  黑棋:
  while(1)
  {
      if(black_flag)
      {
        行棋;
        black_flag=0;
        red_flag=1;
      }
      else
      {
       sleep(1);
      }
   }
 用semophore 有更优雅的表现方式.
 sem_t red,black;
 sem_init(red,0,1);
 sem_init(black,0,0);
 红棋:
  while(1)
  {
    sem_wait(red);  //申请资源
    行棋;
    sem_post(black); //释放黑方资源
   }
 黑棋:
  while(1)
  {
    sem_wait(black); //申请资源
    行棋;
    sem_post(red);  //释放红方资源.
  }
-----------------------
 4.5: 线程的创建与销毁
 -----------------------
   线程创建: 可创建thread_func 线程
   pthread_create(&threadID, NULL, thread_func, NULL);
   线程销毁:  
     1. thread_func, 退出循环,即可自动销毁. return 返回值做为线程返回值
     2. 线程函数中调用thread_exit(val); 来退出, 与return val 一致
     3. 千万不要调用exit(val), 这样不只该线程, 所有线程即整个进程都退出了!
       4. pthread_cancel(threadID), 从别的线程中强制销毁该线程
  说明: 这个threadID 是一个内存地址, unsigned long 类型, 不是linux内核中的threadid
      它的数值等于pthread_self(), 属于pthread 概念范畴.
      用ps -p pid -T 显示的是linux 内核中的threadid, 代码中可以用gettid()函数得到
  有时候, 一个线程由于其特殊性不能销毁, 但需要它处于一个已知的可控的状态,
   此时可以使用全局变量,控制线程的循环,当发现变量为真时,就进入sleep状态
   并返回一个已进入sleep 标志, 这样外部程序知道它没有执行敏感代码,
   从而可以执行一些敏感操作. 然后再放开控制变量,这样当线程醒来并可执行时,
   环境已经更新. 这个操作,等价于销毁,重建.