【线程安全】用户级,内核级,组合级线程|线程同步的处理(条件变量)|strtok_r(可冲入函数)

用户级线程,
内核级线程,线程创建依靠操作系统,是否支持。
组合级线程:同时拥有用户级线程和内核级线程。内核级的线程数受凑cpu数目的影响,而用户 是通过直接调用用户线程库中的线程,只是用一个cpu,当创建的线程数超过cpu的数目,可以使用用户创建更多的线程。
进程对内核的所有访问,都需要接口
linux 其实本身并没有线程的概念,所谓线程就是可以共享某一资源(地址空间)的进程

  • 条件变量
    提供了一种线程间通知的机制,由程序员控制,控制其值
    只有进程进入条件变量得等待队列,才能被唤醒,如果没有,即便唤醒,也唤醒不了,所以直接宣布失败,

请添加图片描述

线程安全:必须使用线程安全的函数

  • 定义:多线程程序,不论调度顺序,只要保证得到一个正确的数据结果,就是安全的。
  • 如何保证线程安全:
  1. 同步:信号量 ,互斥锁,读写锁,条件变量
  2. 使用线程安全的函数(可重入函数
  3. 原因:线程安全问题都是由全局变量静态变量引起的线程共享的数据)。
  4. 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

strtok || strtok_r

 #include<string.h>`
`char *strtok(char *strToken, const char *strDelimit );
  • strtok将字符串分解为标记,strToken为要分解的字符串,strDelimit为分隔符字符串(如果传入字符串,则传入的字符串中每个字符均为分割符)。首次调用时,将要解析的字符串地址作为第一个参数strToken传递进去,之后再次调用要把strToken设成NULL。当strtok函数到达s1的末尾时,就返回NULL。
  • strtok函数如何记录位置
    strtok函数中存在一个static的指针SAVE_PTR用来保存下一次调用中将作为起始位置的地址。
  • strtok缺点
    从实现原理我们可以看出,strtok函数是在原字符串本身上进行操作,破坏待分解字符串的完整性,调用前和调用后的strToken已经不同。因此,如果需要在调用该函数后访问原来的s1,就必须传递字符串的一个拷贝。
  • strtok线程不安全原因
    线程不安全的原因大多是因为数据的共享(全局,静态),那么我们可以看到strtok中为了保存下一个需要解析的字符串的地址,使用了一个静态的指针SAVE_PTR,那么这个指针就是根源啦。由于这个SAVE_PTR只有一个,那么,在同一个程序中出现多个解析不同字符串的strtok调用时,各自的字符串的解析就会互相干扰。
  • strtok_r可重入函数实现原理
    strtok_r函数它在实现的时候并不是直接调用那个SAVE_PTR指针,而是我们在自己的线程中定义一个栈区指针,将SAVE_PTR值拷贝到我们定义的这个栈区指针(本地),就可以用我自己定义的这个本地指针进行操作,而不会收到其他线程的干扰了。
#include<string.h>
char *strtok_r(char *str, const char *delim, char **saveptr);
  1. str为要分解的字符串,delim为分隔符字符串。char **saveptr参数是一个指向char *的指针变量,用来在strtok_r内部保存切分时的上下文,即保存自己线程里的SAVE_PTR的地址,之后切割的时候切割开始点是从我们自己定义的这个saveptr中获取,不会收到其他线程的影响(因为栈区数据不共享)。

  2. 第一次调用strtok_r时,str参数必须指向待提取的字符串,saveptr参数的值可以忽略。连续调用时,str赋值为NULL,saveptr为上次调用后返回的值,不要修改。strtok_r实际上就是将strtok内部隐式保存的SAVE_PTR指针,以参数的形式与函数外部进行交互。由调用者进行传递、保存甚至是修改。需要调用者在连续切分相同源字符串时,除了将str参数赋值为NULL,还要传递上次切分时保存下的saveptr。
    代码演示:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void *fun(void *arg)
{
         char buff[] = "a b c d e f";
         char* ptr = NULL;
      //   char*s = strtok(buff," ");
         char *s = strtok_r(buff," ",&ptr);//返回的值是指向首元素的指针
         while(s != NULL)                  //非线程安全的函数
         {                                   //strtok_r()线程安全函数
                  printf("fun s = %s\n ",s);
                  fflush(stdout);
                  sleep(1);
                  s = strtok_r(NULL," ",&ptr);
          }
 }
int main()
{
 pthread_t id;
 pthread_create(&id,NULL,fun,NULL);
  
       char Arr[] = "1 2 3 4 5 6";
       char *ptrs = NULL;
       //*p = strtok(Arr," "); 错误
       char *p = strtok_r(Arr," ",&ptrs);
        while(p != NULL)
      { 
              printf("p = %s\n ",p);
              fflush(stdout);    //strtok内部的指针,本身就是静态全局指针,因此才能记录上一次被使用的位置。  当两个线程都使用strtok,使用的是同一个静态全局指针,因此就对字符的解析出现错误,另一个线程就会不可预期的修改当前线程的数据
              sleep(1);                 //无法通过加锁解决,因为共用同一块空间,
     //         p = strtok(NULL," ");  //由于strtok继承上一次指针分割的位置,因此,在子线程执行后,两个线程共享同一片空间,另一个线程就会不可预期的修改当前线程的数据,所以,它的指针指向的也就都是buff中的数据
              p = strtok_r(NULL," ",&ptrs); 
      }
        pthread_join(id,NULL);
        exit(0);
 }

错误结果:
请添加图片描述

  • 原因:strtok中为了保存下一个需要解析的字符串的地址,使用了一个静态的指针SAVE_PTR,那么这个指针就是根源啦。由于这个SAVE_PTR只有一个,那么,在同一个程序中出现多个解析不同字符串的strtok调用时,各自的字符串的解析就会互相干扰。

正确结果:请添加图片描述

  • 解决方案:不直接调用那个SAVE_PTR指针,而是我们在自己的线程中定义一个栈区指针,将SAVE_PTR值拷贝到我们定义的这个栈区指针(本地),就可以用我自己定义的这个本地指针进行操作,而不会收到其他线程的干扰了。(栈区的数据不会共享)

fork

在设计的时候,在多线程 中调用,只执行一条路径,
(多线程程序,fork,整个进程资源被复制,只启用一条执行路径)
如果父进程中有互斥锁,那么fork以后,也会后互斥锁
是否加锁,解锁取决于fork时,父进程锁的状态。(对资源的问题)
#include<pthread.h>

void *fun(void *arg)
{
        for(int i =0;i<5;i++)
        {
      printf("fun PID =%d\n",getpid());
      sleep(1);
 }
}
int main()
{
        pthread_t id;
        pthread_create(&id,NULL,fun,NULL);
        fork();
        for(int i =0;i<5;i++)
         {      
        printf("main PID =%d\n",getpid());
        sleep(1);
   }
exit(0);
 }

请添加图片描述

最好在一个最恰当的状态,去fork

参考《高性能服务器编程》–多线程环境 14.8
strtok部分参考 博主「Eunice_fan1207」https://blog.csdn.net/Eunice_fan1207/article/details/83574936

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值