多进程多线程基础

一、概念

1 概念

(1)进程

1)进程是操作系统资源分配的基本单位

2)一个进程可以有多个线程,但至少有一个线程

3)资源分配给进程,同一进程的所有线程共享该进程的所有资源

4)同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),拓展段(堆存储)

5)进程有独立的地址空间

(2)线程

1)线程是任务调度和执行的基本单位

2)一个线程只能属于一个进程

3)每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量或临时变量

4)CPU分给线程,即真正在CPU上运行的是线程(通过CPU调度,在每个时间片中只有一个线程执行)

5)线程之间没有单独的地址空间

2 Linux虚拟内存结构

 

(1)代码段(Text Segment

保存CPU将要执行的机器指令

(2)已初始化数据段(initialized data segment

保存程序中被初始化的全局或静态变量

(3)未初始化数据段(uninitialized data segment

也被称为BSS(block started by symbol),这个段中的数据在程序执行之前被内核初始化为0或者NULL

(4)栈(Stack

存储临时变量,函数相关信息

(5)堆(heap

动态内存分配位置。堆的位置位于未初始化数据段和栈的中间

3 进程的创建

(1)exec

当进程调用一种exec函数时,原进程完全由新程序代换,而新程序则从其main函数开始执行。

(2)fork

新进程几乎与原进程一模一样,执行的代码也完全相同,但是新进程有自己的数据空间、环境和文件描述符。

(3)system

使用system函数远非启动其他进程的理想手段,因为它必须用一个shell来启动需要的程序。

特点:

1)建立独立进程,拥有独立的代码空间,内存空间;

2)等待新的进程执行完毕,system才返回(阻塞)。

4 线程的创建

4.1  创建

(1)接口

int pthread_create(pthread_t *thread,  const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

(2)返回值

成功返回0,失败返回错误号。

在一个线程中调pthread_create()创建线程后,当前线程从pthread_create()返回继续往下执行,新的线程所 执行的代码由我们传给pthread_create()的函数指针start_routine()决定。

4.2 终止

(1)接口

void pthread_exit(void *retval)函数用于进程终止

retval是void*类型,其它线程可以调pthread_join获得这个指针

(2)终止线程的3种方法

从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit

一个线程可以调用pthread_cancel()终止同一进程中的另一个线程

线程可以调pthread_exit()终结自己

4.3 容易混淆的两个“线程ID”

(1)内核中的ID

1)属于调度范畴

2)本质为一个整数

3)此ID数据类型为pid_t

4)通过syscall(SYS_gettid)获取

5)对于不同进程之间的所有线程,在任意时刻此ID全局唯一

6)在procfs文件系统中,可通过查看/proc/pid/task/tid获取对应的线程信息

(2)POSIX中的ID

1)属于线程库范畴

2)本质为一个地址

3)此ID数据类型为pthread_t

4)通过pthread_self()获取

5)不同进程之间此ID不唯一,因此此ID仅在同一进程内有意义

4.4 线程分离

(1)非分离状态

1)原有的线程等待创建的线程结束

2)只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源

(2)分离状态

1)没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源

2)2种方法可以设置线程分离状态

a)在线程创建时将其属性设为分离状态(detached),调用函数:pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

b)在线程创建后将其属性设为分离状态(detached),调用函数:pthread_detach(pthread_self())

二、竞态资源保护

1 竞态条件、竞态资源

(1)竞态条件

当两个或多个线程/进程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件(race condition)。

程序运行结果取决于多线程/多进程的执行顺序。

(2)竞态资源

竞态条件下多线程争抢的是“竞态资源”、“临界资源”。

导致竞态条件发生的代码片段称作“临界区”。

在临界区中使用适当的同步就可以避免竞态条件。

(3)竞态资源的主要保护手段

锁机制(lock)是多线程编程中最常用的同步机制,用来对多线程间共享的临界区(Critical Section)进行保护。

2 常用同步工具

2.1 互斥锁

(1)基本操作

1)原理

在编程中,引入对象互斥锁的概念,来保证共享数据操作的完整性。

2)创建

pthread_mutex_init();

3)加锁

pthread_mutex_lock(); pthread_mutex_trylock();

4)解锁

pthread_mutex_unlock();

5)销毁

pthread_mutex_destory();

(2)死锁概念

1)如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,产生死锁(dead lock)。

2)若线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。

(3)必要条件

1)互斥条件

一个资源每次只能被一个进程使用

2)请求与保持条件

一个进程因请求资源而阻塞时,对已获得的资源保持不放

3)不剥夺条件

进程已获得的资源,在未使用完之前,不能强行剥夺

4)循环等待条件

若干进程之间形成一种头尾相接的循环等待资源关系

(4)使用规则

1)识别需要保护的数据后用锁保护

2)锁只允许 保护数据,不允许保护过程

3)在同一模块、同一抽象层中加锁和释放锁

4)控制锁的数量

5)在等待某个资源时,使用超时机制

6)控制锁的范围不要太大

2.2 信号量

(1)基本操作

1)原理

用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。

2)创建

sem_init(); sem_open();

3)阻塞等待

sem_wait();

4)试图等待

sem_trywait();

5)释放

sem_post();

6)销毁

sem_destory(); sem_close(); sem_unlink();

(2)特性

1)信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问;

2)当公共资源增加时,调用函数sem_post()增加信号量;

3)只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量;

4)信号量可以是二元的,也可以是多元的,通过初始化时指定;

5)信号量允许多个线程共同等待一个共享资源。

(3)互斥锁、信号量区别

1)互斥锁

只允许一个线程进入临界区

2)信号量

允许多个线程同时进入临界区

(4)无名、有名信号量区别

1)无名信号量

创建sem_init();

销毁sem_deatory();

存在于内存中;

2)有名信号量

创建sem_open();

销毁sem_close() sem_unlink()

通过名字访问

3)注意事项

同一进程的不同线程间建议使用无名信号量;

采用无名信号量时,使用完毕注意通过sem_destory()销毁;

采用有名信号量时,使用完毕注意通过sem_unlink()销毁。

2.3 条件变量

(1)基本操作

1)原理

在编程中,条件变量用于等待

2)创建

pthread_cond_init();

3)等待

pthread_cond_wait(); pthread_cond_timedwait();

4)激活

pthread_cond_signal(); pthread_cond_broadcast();

5)销毁

pthread_cond_destory();

(2)等待、激活规则

1)等待线程

pthread_mutex_lock(&mutex);

while(线程执行的条件是否成立)

pthread_cond_wait(&cond, &mutex);

//处理共享资源

pthread_mutex_unlock(&mutex);

2)激活线程

pthread_mutex_lock(&mutex);

pthread_cond_signal(&cond);  //激活一个等待该条件的线程,存在多个线程时按入队顺序激活其中一个

pthread_mutex_unlock(&mutex);

2.4 自旋锁

(1)基本操作

1)原理

自旋锁主要为在短时间内快速执行轻量级的加锁

2)创建

pthread_spin_init();

3)加锁

pthread_spin_lock(); pthread_spin_trylock();

4)解锁

pthred_spin_unlock();

5)销毁

pthread_spin_destory();

(2)特性

1)忙等待:连续测量一个变量直到某个值出现为止;

2)表示一直在原地旋转,并未去睡眠。可能存在的问题:死锁,过多占用cpu资源等;

3)只有在有理由认为等待时间非常短的情形下,才只用忙等待;

4)自旋锁的设计初衷,在短时间内进行轻量级加锁;

5)自旋锁不能够在能够导致睡眠的环境中使用。

(3)使用场景

临界区可以快速完成读写,并且读写概率不定。

(4)自旋锁与互斥锁区别

1)互斥锁

sleep-waiting

让权等待

pthread_mutex_unlock();

资源被占用,资源申请者就会进入睡眠状态

2)自旋锁

busy-waiting

忙等待

pthread_spin_unlock();

一直在原地旋转,并未去睡眠

2.5 读写锁

(1)基本操作

1)原理

读写锁是一种特殊的自旋锁,它把对共享资源的访问者划分为读者和写者

2)创建

pthread_rwlock();

3)加锁

pthread_rwlock_relock() pthread_rwlock_wrlock()

pthread_rwlock_tryrdlock() pthread_rwlock_trywrlock()

4)解锁

Pthread_rwlock_unlock()

5)销毁

Pthread_rwlock_destory()

(2)特性

1)本质为一种特殊的自旋锁;

2)多个读者可以同时进行读(即读并行);

3)写者必须互斥(只允许一个写者写,也不能读者、写者同时进行,即写串行);

4)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者,即写的优先级高);

5)自旋锁不能够在能够导致睡眠的环境中使用。

(3)读写锁、互斥锁比较

1)互斥锁

读写串行

只有一个线程拥有互斥锁

pthread_mutex_unlock();

2)读写锁

读并行,写串行

可多个线程同时拥有锁

pthread_rwlock_unlock();

3)说明

读写锁也叫共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当读写锁以写模式锁住时,它是以独占模式锁住的。

(4)使用场景

读的概率远远大于写的概率。

三、进程间通信

1 进程、线程间通信的概念

(1)进程间通信

a)进程用户空间是相互独立的,一般而言是不能相互访问的;

b)但很多情况下进程间需要互相通信,来完成系统的某项功能;

c)进程通过与内核及其他进程之间的相互通信来协调他们的行为。

 

(2)线程间通信

a)同步:同步就是协调步调,按预定的先后次序进行运行;

b)互斥:线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,知道占用资源者释放该资源。

c)线程互斥可以看成是一种特殊的线程同步(统称为同步)。

2 通信类工具

 

3通信方式

(1)进程间通信方式

管道(Pipe)

有名管道(Named Pipe)

信号(Signal)

信号量(Semophore)

消息队列(Message Queue)

共享内存(Shared Memory)

套接字(Socket)

(2)线程间通信方式

锁机制(互斥锁、条件变量、读写锁)

信号量机制(Semaphore,无名线程信号量和命名线程信号量)

信号机制(Signal,类似进程间的信号处理)

4通信方式差异

(1)进程间通信

a)实现方式:

需要通过内核调度实现

b)主要场景/目的:

传递消息

数据交换

(2)线程间通信

a)实现方式:

无需通过内核调度

b)主要场景/目的

线程同步

线程互斥

5消息队列

(1)基本操作

原理:是消息链表,是一种内核资源,使用前需要预分配消息缓存。主要用于从一个线程向另外一个线程发送数据块。

创建:mq_open

发送:mq_send

接收:mq_receive

关闭:mq_close

销毁:mq_unlink

(2)特性

1)读消息与写消息异步,它允许读消息者在消息发送很长时间后再读取消息,即使写消息者写完消息后终止该消息队列(mq_close),读消息者仍能在后续某个时间打开消息队列(mq_open)来读取消息。我们常需要读消息者一直轮询该消息队列才能读取到最新消息;

2)消息队列承载的信息是有格式的数据,如特定的结构体或字符串信息等;

3)消息可由写消息者赋予一个优先级,读消息者读取到的总是最高优先级的最早消息;

4)相较于信号,消息队列能够传递更多的数据,但仍然有大小限制;

5)消息队列具有随内核的持续性,消息队列对象一直在内存直到内核重启或显式删除该对象为止。

(3)注意事项

1)mq_系列函数不是标准c库函数,链接时需要指定链接库,通过-lrt链接

2)通过mq_open创建消息队列时,名称必须以/开头,并且后面不能还有/

3)消息队列的名字中打头的/字符不计入长度

4)通过mq_open失败,错误码为38,表示内核不自持消息队列(可通过cat/proc/sys/fs确认)

5)mq_close仅是关闭消息队列描述符,与close一样,仅关闭消息队列,不会删除消息队列

6)未执行mq_unlink可能出现创建新消息队列时资源不足,报错errno[24:Too many open files.]

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值