线程私有数据TSD——一键多值技术,线程同步中的互斥锁和条件变量
阅读原文时间:2023年07月08日阅读:1

一:线程私有数据:

线程是轻量级进程,进程在fork()之后,子进程不继承父进程的锁和警告,别的基本上都会继承,而vfork()与fork()不同的地方在于vfork()之后的进程会共享父进程的地址空间,但是有了写实复制(fork()之后的子进程也不会直接复制父进程的地址空间在另一块内存,除非子进程修改了某种信息,才会在内存中为它重新复制一块区域)的出现之后,vfork()基本上就没什么作用了。但是线程与进程相比,还是有许多优点,比如节约资源,它复制的东西比vfork()之后的进程复制的东西还要少;并且多线程共享数据空间,所以通信就容易;还共享地址空间,所以线程之间的切换就快。线程的私有数据是在与其他线程共享进程数据的背景下,自己所拥有的信息,有以下几点:

  • 线程号(无符号长整形(unsigned long))
  • 一组寄存器的值(与cpu之间数据交换最快,线程会在运行的时候得到内核分配的寄存器,时间片完之后会把结果写入内存,寄存器会被回收。)
  • 堆栈
  • 错误处理
  • 信号屏蔽字
  • errno变量
  • 调度优先级
  • 私有的存储空间
  • 私有数据(TSD,一键多值技术,接下来我们重点看看)

这个私有数据还挺多的,下面我们主要看看私有数据的最后一条,TSD,一键多值技术。首先我们解释一下为什么要用它:

虽然线程之间共享数据空间为程序带来了便利,但是我们线程内部也会有这种需求:假设我们线程1内部有3个函数,而这三个函数需要对同一个变量 i 操作,那么这个变量 i 就可以看作是线程1内部自己的“全局变量“,但是我们如果在主线程中定义 i 为全局变量,而另一个线程2中也需要定义和 i 一样的变量(并且i在线程1和线程2之中的意义也相同),那么这个主线程中的全局变量 i 就能被两个线程访问,这样一来,线程1访问到的 i 可能刚好被线程2修改过,就会产生写无法预知的结果。(举个例字:errno变量,我们每个线程都拥有errno变量,但是每个线程的errno变量都不同,而且对于某一个线程内部,errno变量就是“全局“的,线程中的任何函数都可以修改它)

为了避免上述问题,我们真正的在主线程中引入了这样一个全局变量,就是我们的”key”——键,即一个键对应多个数值,每个线程都可以去访问这个键,只是不同的线程访问到的是属于自己的不同与别的线程的数据。一键多值技术靠的是一个关键的数据结构,即TSD池,也就是TSD池为我们提供了全局变量”key”。下面我们看一段代码(代码中包含了4个常用函数)

pthread_key_t  key;                          //申请一个key
void *func2()
{
    int tsd = 2;
    pthread_setspecific(key,(void*)&tsd);       //将key与pthread2的变量tsd相关联
    printf("i am thread2 :%u,my tsd:%d\n",pthread_self(),tsd);//输出pthread2的变量
    pthread_exit(0);
}

void *func1()
{
    int tsd = 0;
    int *status2;
    pthread_t tid2;

    pthread_setspecific(key,(void*)&tsd);       //将key与pthread1的变量tsd相关联
    printf("i am thread1 :%u,my tsd:%d\n",pthread_self(),tsd);//输出pthread1的变量
    pthread_create(&tid2,NULL,func2,NULL);
    pthread_join(tid2,(void**)&status2);
    pthread_exit(0);

}
int main(int argc,char *argv[])
{
    pthread_t  tid1,tid2;
    int *status;
    pthread_key_create(&key,NULL);              //创建一个key

    pthread_create(&tid1,NULL,func1,NULL);
    pthread_join(tid1,(void**)&status);
    pthread_key_delete(key);                    //删除key

    exit(0);
}

输出结果如下:
[kiosk@yangbodong 20150804]$ ./a.out
i am thread1 :2131724032,my tsd:0
i am thread2 :2123331328,my tsd:2
虽然thread1和thread2都关联的是同一个key,变量都是tsd,但是它们访问到的值是不同的。

二:互斥锁和条件变量。
线程最大的特点就是资源的共享性,但是同步问题又是资源共享的一个难点,所以就有了互斥锁和条件变量。
1:互斥锁

下面谈谈我对互斥锁的理解:首先它是锁,而且它是用来锁别人的,也就是当你访问某个文件的时候,你可以把别人锁在文件外边,不让他访问,当你访问完成后,解锁,这时,锁还在文件那里,下一个线程谁过去首先抢到锁,就可以把别人锁到外边,自己访问,这样可以解决多线程对一个文件同时访问时出错。

下面我们看一段代码(代码中包含了3个常用函数)

pthread_mutex_t  number_mutex;                      //定义一把锁
int              globvar = 1;                       //定义一个全局变量globvar = 1

void *func1()
{
    pthread_mutex_lock(&number_mutex);              //加锁函数
    globvar++;
    printf("thread1---globvar:%d\n",globvar);
    pthread_mutex_unlock(&number_mutex);            //解锁函数
    pthread_exit(0);
}
void *func2()
{
    pthread_mutex_lock(&number_mutex);
    globvar++;
    printf("thread2---globvar:%d\n",globvar);
    pthread_mutex_unlock(&number_mutex);
    pthread_exit(0);
}
void *func3()
{
    pthread_mutex_lock(&number_mutex);
    globvar++;
    printf("thread3---globvar:%d\n",globvar);
    pthread_mutex_unlock(&number_mutex);
    pthread_exit(0);
}

int main(int argc,char *argv[])
{
    pthread_t  tid1,tid2,tid3;
    void *status1;
    void *status2;
    void *status3;

    pthread_mutex_init(&number_mutex,NULL);       //对锁进行初始化
    pthread_create(&tid1,NULL,func1,NULL);
    pthread_create(&tid2,NULL,func2,NULL);
    pthread_create(&tid3,NULL,func3,NULL);
    pthread_join(tid1,(void**)&status1);
    pthread_join(tid2,(void**)&status2);
    pthread_join(tid3,(void**)&status3);
    exit(0);

}
下面是运行结果:
[kiosk@yangbodong 20150804]$ ./a.out
thread1---globvar:2
thread2---globvar:3
thread3---globvar:4
[kiosk@yangbodong 20150804]$ ./a.out
thread2---globvar:2
thread1---globvar:3
thread3---globvar:4
注意:这两次运行结果不仅说明了加锁保证了某一时刻只有一个线程去访问globvar,而且说明了他们线程抢到锁的顺序也是随机的,不确定的。

2:条件变量

条件变量是利用线程间共享全局变量的一种机制,它在宏观上就相当于if语句,满足条件执行,不满足,等待条件成立。主要包含两个动作:一个动作是等待条件变为真,另一个是执行后设置条件为真,为了保证条件变量可以被准确的修改,互斥锁就扮演了重要角色,我对全局变量的修改加锁,当我的锁被释放的时候,我让等待条件变为真,这时候就可以保证修改的准确性。

下面我们看一段代码:

pthread_mutex_t  mutex;
pthread_cond_t   cond;
int              var = 0;
void *func2()
{
    while(var < 10)
    {
            printf("thread2 is runninig\n");
            pthread_mutex_lock(&mutex);
            if(var%2 !=  0)
                pthread_cond_wait(&cond,&mutex);//如果是奇数就让它阻塞
            printf("pthread 2 var :%d\n",var); //如果是偶数,就让它输出
            pthread_mutex_unlock(&mutex);
            sleep(1);
    }
}

void *func1()
{
    pthread_cleanup_push(pthread_mutex_unlock,&mutex);
    for(var = 0;var <= 10;var++)
    {

            printf("thread1 is running\n");
            pthread_mutex_lock(&mutex);
            if(var%2 == 0)  //如果是偶数,就发出信号,唤醒上面的wait,但是wait能不能
                pthread_cond_signal(&cond); //收到信号与它是没有关系的,它就会继续往下执行
            else
                printf("pthread 1 var :%d\n",var);
            pthread_mutex_unlock(&mutex);
            sleep(1);

    }
    pthread_cleanup_pop(0);
}

int main(int argc,char *argv[])
{
    pthread_t   tid1,tid2;
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_create(&tid1,NULL,func1,NULL);
    pthread_create(&tid2,NULL,func2,NULL);

    sleep(10);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
}
下面是运行结果:
thread2 is runninig
pthread 2 var :0
thread1 is running
thread2 is runninig
thread1 is running
pthread 1 var :1
thread1 is running
pthread 2 var :2
thread1 is running
pthread 1 var :3
thread2 is runninig
thread1 is running
pthread 2 var :4
thread1 is running
pthread 1 var :5
thread2 is runninig
thread1 is running
pthread 2 var :6
thread1 is running
pthread 1 var :7
thread2 is runninig
thread1 is running
pthread 2 var :8
thread1 is running
pthread 1 var :9
thread2 is runninig
从结果我们可以验证,如果调用pthread_cond_wait()函数,我们的此线程就会阻塞在那里,等待一个signal将它激活,当然,这只是激活一个线程,如果要有多个,我们还可以使用pthread_cond_broadcast函数。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章