多线程之间共享的资源,进程间通信和线程间通信,

多线程之间共享的资源

共享的资源有

a. 堆  由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)

b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的

c. 静态变量 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的

d. 文件等公用资源  这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。

独享的资源有

a. 栈 栈是独享的

b. 寄存器  这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC

线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。

 

   进程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能实现并发性。这些个性包括:


   1.线程ID
      每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标

   识线程。

 

   2.寄存器组的值
      由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线

   程切换到另一个线程上 时,必须将原有的线程的寄存器集合的状态保存,以便

   将来该线程在被重新切换到时能得以恢复。

 

   3.线程的堆栈
      堆栈是保证线程独立运行所必须的。
      线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程

   必须拥有自己的函数堆栈, 使得函数调用可以正常执行,不受其他线程的影

   响。


   4.错误返回码
      由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用

   后设置了errno值,而在该 线程还没有处理这个错误,另外一个线程就在此时

   被调度器投入运行,这样错误值就有可能被修改。
      所以,不同的线程应该拥有自己的错误返回码变量。


   5.线程的信号屏蔽码
      由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自

   己管理。但所有的线程都 共享同样的信号处理器。


   6.线程的优先级
      由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参

   数,这个参数就是线程的 优先级。

inux 进程之间的通信方式

进程间的通信方式(IPC):

1)管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

2)有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3)信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

4)消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并有消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5) 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。信号是进程间通信机制中唯一的异步通信机制

6) 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。

7)套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。

线程间的通信通信方式:

1.锁机制:包括互斥锁、条件变量、读写锁

   互斥锁提供了以排他方式防止数据结构被并发修改的方法。

   读写锁允许多个线程同时读共享数据,而对写操作是互斥的。

   条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

2.信号量机制(Semaphore):包括无名线程信号量和命名线程信号量

3.信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

 

*******************************************************************************

 

1)无名管道

管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。

举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。

 

2)有名管道

命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。

由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。

创建命名管道:mkfifo

#include <sys/types.h>  

#include <sys/stat.h>  

int mkfifo(const char *filename, mode_t mode); 

注意是创建一个真实存在于文件系统中的文件,filename指定了文件名,而mode则指定了文件的读写权限

访问有名管道: open

注意:1、就是程序不能以O_RDWR模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程就会读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递。2、就是传递给open调用的是FIFO的路径名,而不是正常的文件。

打开FIFO文件通常有四种方式:

open(const char *path, O_RDONLY); 

open(const char *path, O_RDONLY | O_NONBLOCK);

open(const char *path, O_WRONLY);

open(const char *path, O_WRONLY | O_NONBLOCK); 

open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

 

3)消息队列

KEY值的创建

key_t ftok(const char *pathname, int proj_id);

功能:生成一个KEY值

参数:pathname 路径名

proj_id 任意值

返回值: 成功 key 值

失败 -1

创建和访问一个消息队列:int msgget(key_t, key, int msgflg)。

程序必须提供一个key来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1

 

把消息添加到消息队列中:

int msgsend(int msgid,const void *msg_ptr, size_t msg_sz, int msgflg) 

msgid是由msgget函数返回的消息队列标识符。

msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针 msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:

struct msgbuf {

long mtype; 消息类型

char mtext[1]; 消息正文

};

msg_sz是消息正文的长度。

msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情(

0 阻塞发送)。

如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.

从消息队列中读取消息:

int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);  

msgid, msg_ptr, msg_st的作用也函数msgsnd函数的一样。

msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情(0 阻塞接收)。

调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.

 

消息队列控制函数:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数: msqid 消息队列标识符

cmd IPC_STAT 将消息的属性信息复制给第三个参数

IPC_SET 设置消息的属性信息给第三个参数

IPC_RMID 删除一个消息队列,第三个参数为NULL

返回值:成功 0

失败 -1

 

与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

 

4)信号量sem(有时也被称为信号灯)

作用:信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线 程)所拥有。

信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明

它被占用,测试的线程要进入睡眠队列中,等待被唤醒。

信号量与互斥锁之间的区别:

1. 互斥量用于线程的互斥,信号线用于线程的同步。  

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。  

 

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。  

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源  

 

2. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

3. 互斥量值只能为0/1,信号量值可以为非负整数。  

 

信号量可以被抽象为五个操作:

1 初始化 sem_init 2 申请资源(P操作)sem_wait

3 释放资源(V操作)sem_post 4 摧毁信号量sem_destroy

5 获取当前信号量的值sem_getvalue

 

P操作:线程等待信号量,如果值大于0,则获得,值减一;如果只等于0,则一直线程进入睡眠状态,知道信号量值大于0或者超时。

V操作:执行释放信号量,则值加一;如果此时有正在等待的线程,则唤醒该线程

附加函数:sem _trywait 如果调用TryWait,线程并不真正的去获得信号量,还是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功;否则返回失败。  

 

5 互斥锁(mutex)

Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待;值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0

 

互斥锁操作:

1 初始化 pthread_mutex_init 2 上锁 pthread_mutex_lock

3 释放锁 pthread_mutex_unlock 3 摧毁锁 pthread_mutex_destroy

 

Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁(系统一般会在第二次调用立刻返回)。也就是说,加锁和解锁这两个对应的操作,需要在同一个线程中完成。  

 

6 共享内存(shm) :

共享内存允许两个不相关的进程访问同一个逻辑内存。

共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。但是它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。我们通常是用共享内存来提供对大块内存区域的有效访问,同时通过传递小消息来同步对该内存的访问。

在第一个进程结束对共享内存的写操作之前,并无自动的机制可以阻止第二个进程开始对它进行读取。对共享内存访问的同步控制必须由程序员来负责。

对共享内存的操作函数:

1 创建或者打开共享内存 shmget 2 映射共享内存 shmat

3 撤销共享内存 shmdt 4 删除共享内存 shmctl

 

映射共享内存详解 void *shmat(int shmid, const void *shmaddr, int shmflg)

参数:shmid shmget的返回值

shmaddr NULL 默认自动寻找

hmflg 0 可读可写

返回值:成功 共享内存区域的地址

失败 -1

7)套接字(socket):

源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务

套接字,是支持TCP/IP网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

非常非常简单的举例说明下:Socket=Ip address+ TCP/UDP + port。

 

 

 

 

 

 

 

转自 https://blog.csdn.net/u014558484/article/details/52550678

        https://blog.csdn.net/m0_37759974/article/details/89850679

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值