一、计算机内存布局
-
文本区(程序代码区):这是二进制代码的存储位置,主要包含程序的执行指令。为防止数据被意外修改,此区域通常被设置为只读。
-
数据区:这是全局变量和静态变量的存储位置,可进一步分为已初始化数据区(.DATA)和未初始化数据区(.BSS)。
- 已初始化数据区(.DATA):如果全局变量或静态变量在声明时被赋予了初值,那么它们会被存放在此区域。
- 未初始化数据区(.BSS):BSS(Block Started by Symbol)是指在程序开始运行之前,计算机会把此区域的数据全部设置为0。如果我们在C++中声明了静态变量或全局变量但没有初始化,那么编译器将会将这些变量标记为BSS区的数据,程序运行时,这些变量会被自动初始化为0。
-
堆。堆是进程的一部分,它是程序运行时动态分配内存的区域。一个进程启动时,操作系统将会为其分配一个私有的虚拟内存空间,同一个进程的所有线程都会看见并使用这块空间。
-
文件等共用资源。
【独享的资源有】
-
栈 栈是独享的
-
寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC
线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。[1]
为什么线程常常和“不安全”在一起?
- 原子性:一个或者多个操作在 CPU 执行的过程中被中断
- 可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到
- 有序性:程序执行的顺序没有按照代码的先后顺序执行[2]
张三及其女友约好共同做好这一道"红烧排骨",男友负责食材处理、放调料、开火,女友负责收汁装盘。
- 原子性,张三正在执行放盐操作45%,电话响了,张三放下了手中的勺子接电话,因为张三放盐操作执行了45%,因为无法评估放盐执行动作,放盐操作是“原子性”的,只能是放盐和成功和失败,于是一份不合格料理出来了“偏咸或偏淡”的红烧排骨,于是两人分手;
- 可见性,本着时间利用的原则,女朋友在男朋友准备食材、开火和放调料时,决定去看看电视剧,男朋友完成其工作后,没有告知的情况下取快递了,于是到饭点了,女朋友认为男友尚未准备好食材,于是两人吃了白米饭后分手了;
- 有序性,这道菜显然是由先后顺序的,女朋友在男友尚未完成其工序的时候,直接收汁装盘,男朋友看着摆在桌子前生的排骨,决定分手。
在这个例子上,两个人一起做“红烧排骨”这道菜,就是多线程任务,由于做饭的原子性、可见性和有序性的要求,会直接影响这道菜的好坏进而导致分手还是牵手,所以说一起做菜是“不安全”的,容易分手。
什么是轮询?
轮询就是女朋友一直在查询男朋友有没有做完他的工作,做完了就开始完成自己的任务了,这个方法的缺点是,女朋友无法看剧。
下面展示这个程序,完成了A B协调,A打印后B打印。设置了一个全局变量value,value更像是一种标记,当对方线程尚未完成其操作时将会等待,当对方线程完成就会让对方等待,自己执行。
程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
char message[]="Hello Thread World";
int value=1;
int count1=0;
void *thread1(void *arg)
{
//int count=0;
while(count1++<20)
{
if(value==1)
{
value=2;
printf("%d",value);
}
else
{
sleep(1);
}
}
pthread_exit(NULL);
}
int count2=0;
void *thread2(void *arg)
{
//int count=0;
while(count2++<20)
{
if(value==2)
{
value=1;
printf("%d",value);
}
else
{
sleep(1);
}
}
pthread_exit(NULL);
}
int main()
{
int res;
pthread_t th1,th2;
void *thread_result;
res=pthread_create(&th1,NULL,thread1,(char *)message);
if(res!=0)
{
perror("thread1 creation failed");
exit(EXIT_FAILURE);
}
res=pthread_create(&th2,NULL,thread2,(char *)message);
if(res!=0)
{
perror("thread2 creation failed");
exit(EXIT_FAILURE);
}
res=pthread_join(th1,NULL);
if(res!=0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
res=pthread_join(th2,NULL);
if(res!=0)
{
perror("Thread join failed");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
小结
这种简单的通过标志位+经验延时来完成的同步,加延时的目的是保证线程不在同一时间访问同一个普通变量value,因为普通变量不具有原子性,只能通过时间保证变量已经稳定(盐放完了)。下一篇文章,将会讲到一种特殊的变量——信号量,他只需要等待有限、固定的时长(等待信号锁资源释放)就可以完成线程之间的同步。