一:线程私有数据:

线程是轻量级进程,进程在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函数。

线程私有数据TSD——一键多值技术,线程同步中的互斥锁和条件变量的更多相关文章

  1. ZT linux 线程私有数据之 一键多值技术

    这个原作者的这个地方写错了 且他举的例子非常不好.最后有我的修正版本 pthread_setspecific(key, (void *)&my_errno); linux 线程私有数据之一键多 ...

  2. linux 线程的同步 二 (互斥锁和条件变量)

    互斥锁和条件变量 为了允许在线程或进程之间共享数据,同步时必须的,互斥锁和条件变量是同步的基本组成部分. 1.互斥锁 互斥锁是用来保护临界区资源,实际上保护的是临界区中被操纵的数据,互斥锁通常用于保护 ...

  3. linux线程私有数据---TSD池

    进程内的所有线程共享进程的数据空间,所以全局变量为所有线程共有.在某些场景下,线程需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Data)TSD来解决.在线程内部, ...

  4. 进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)

    注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...

  5. linux c 线程间同步(通信)的几种方法--互斥锁,条件变量,信号量,读写锁

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量.信号量和读写锁. 下面是思维导图:  一.互斥锁(mutex)  锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 1 . ...

  6. node源码详解(七) —— 文件异步io、线程池【互斥锁、条件变量、管道、事件对象】

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource7 本博客同步在https://cnodejs.o ...

  7. 非常精简的Linux线程池实现(一)——使用互斥锁和条件变量

    线程池的含义跟它的名字一样,就是一个由许多线程组成的池子. 有了线程池,在程序中使用多线程变得简单.我们不用再自己去操心线程的创建.撤销.管理问题,有什么要消耗大量CPU时间的任务通通直接扔到线程池里 ...

  8. Linux 线程同步的三种方法(互斥锁、条件变量、信号量)

    互斥锁 #include <cstdio> #include <cstdlib> #include <unistd.h> #include <pthread. ...

  9. Linux进程间通信IPC学习笔记之同步一(线程、互斥锁和条件变量)

    基础知识: 测试代码: 参考资料: Posix 多线程程序设计

随机推荐

  1. Linux系列(24) - chmod

    前言 在Unix和Linux的中,每个文件(文件夹也被看作是文件)都有三种权限:读.写.运行. 被授予权限的用户身份有三种:当前文件的拥有者,与拥有者属于同组者(同一个group),其他人 hello ...

  2. django中admin一些方法

    1.概述:内容发布,负责添加,修改,删除内容及公告访问2.配置admin应用在settings文件中INSTALLED_APPS添加:'django.contrib.admin', 默认是已经添加好的 ...

  3. Javascript 常见的高阶函数

    高阶函数,英文叫 Higher Order function.一个函数可以接收另外一个函数作为参数,这种函数就叫做高阶函数. 示例: function add(x, y, f) { return f( ...

  4. Python如何连接Mysql及基本操作

    什么要做python连接mysql,一般是解决什么问题的 做自动化测试时候,注册了一个新用户,产生了多余的数据,下次同一个账号就无法注册了,这种情况怎么办呢?自动化测试都有数据准备和数据清理的操作,如 ...

  5. apiserver源码分析——处理请求

    前言 上一篇说道k8s-apiserver如何启动,本篇则介绍apiserver启动后,接收到客户端请求的处理流程.如下图所示 认证与授权一般系统都会使用到,认证是鉴别访问apiserver的请求方是 ...

  6. Spring面试复习整理

    Spring Spring核心分为三方面: 控制反转(IoC): 就是将创建对象的权利交给框架处理/控制,不需要人为创建,有效降低代码的耦合度,降低了开发成本. 依赖注入(DI): 容器动态地将将某种 ...

  7. Git学习笔记01-安装

    首先,什么是git? git是开源的分布式系统,能够将团队的项目上传至git,供团队修改demo 第一步:安装好git(推荐淘宝镜像下载,地址https://npm.taobao.org/mirror ...

  8. Parameter index out of range(1 > number of parameters, which is 0)参数索引超出范围

    今天在写项目的过程中,有一个模块是做多选删除操作,通过servlet获得多选框的value组,然后执行sql操作.如下: 1 @RequestMapping( "/delteCouse.do ...

  9. 多项式(polynomial)

    多项式(polynomial) 题目大意: 给出一个 n 次多项式 \(f(x)=\sum_{i=0}^na_ix^i\) 对于\(k ≤ x ≤ k + l − 1\) 的\(l\) 个\(x\), ...

  10. 容器化之路Docker网络核心知识小结,理清楚了吗?

    Docker网络是容器化中最难理解的一点也是整个容器化中最容易出问题又难以排查的地方,加上使用Kubernets后大部分人即使是专业运维如果没有扎实的网络知识也很难定位容器网络问题,因此这里就容器网络 ...