浅谈并发、进程通信方式、Go协程三者的简单应用场景

一、概念

(1)并发的概念及其重要性

在早期,CPU都是以单核的形式顺序执行机器指令。Go语言的祖先C语言正是这种顺序编程语言的代表。顺序编程语言中的顺序是指:所有的指令都是以串行的方式执行,在相同的时刻有且仅有一个CPU在顺序执行程序的指令。

随着处理器技术的发展,单核时代以提升处理器频率来提高运行效率的方式遇到了瓶颈,目前各种主流的CPU频率基本被锁定在了3GHZ附近。单核CPU的发展的停滞,给多核CPU的发展带来了机遇。相应地,编程语言也开始逐步向并行化的方向发展。Go语言正是在多核和网络化的时代背景下诞生的原生支持并发的编程语言。

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
并发:并发程序指同时进行多个任务的程序。在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

(2)线程和协程概念的概念及其重要性

线程:线程是操作系统能够进行运算调度的最小单位。一个进程可以包含多个线程,是进程中的实际运作单位。

协程:又称微线程。协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

为什么会诞生协程?

虽然多线程在前互联网世代已经足够使用,但是线程的局限性也比较明显,线程数量有限,一般不会很多,线程占据的资源通常比我们需要的多得多,造成浪费,每个系统级线程开辟都会占用空间,这个空间可能是MB级别,但是我们如果使用的线程只需要传递KB级别数据,那么线程看起来就会比较浪费,但是又不可避免。而且线程之间的切换也会占用一些额外开销。为了解决上面的矛盾问题,协程诞生了:更小的资源开支,动态调配资源,比线程更轻量。

协程的一些优点:

因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

二、进程通信方式

  1. 管道/匿名管道(pipe):管道的实质是一个内核缓冲区
  2. 有名管道(FIFO):先进先出(first in first out),以有名管道的文件形式存在于文件系统中
  3. 信号(Signal) :无需知道该进程的状态、阻塞进程、异步通信
  4. 消息队列(Message Queue):放在内核中的消息链表,允许一个或多个进程向它写入与读取消息。克服了信号承载信息量少缺陷,目前主要有两种类型的消息队列:POSIX消息队列以及SystemV消息队列,系统V消息队列目前被大量使用。
  5. 共享内存(share memory):使得多个进程可以可以直接读写同一块内存空间,是最快的可用IPC形式,由于多个进程共享一段内存,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥
  6. 信号量(semaphore):信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步只能通过两个标准原子操作:wait(semap)
    , signal(semap),进行访问信号量是非负整型变量操作也被成为PV原语(P来源于荷兰语proberen"测试",V来源于荷兰语verhogen"增加",P表示通过的意思,V表示释放的意思)。
  7. 套接字(socket) :套接字是支持TCP/IP的网络通信的基本操作单元,套接字的特性由3个属性确定,它们分别是:域、端口号、协议类型。

在这里插入图片描述
信号量与互斥量之间的区别:
(1)互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

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

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

(2)互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

三、Go协程通讯使用

基本上就是推荐使用channel,这个是最推荐的使用形式;

还有就是使用sync.Mutex互斥锁进行加锁通讯;

四、Go使用协程一些应用场景

进行互不相干的『循环』,需要等待结果计算,这种情况下,一般是不同『数据集合』需要进行『处理』,在处理的过程中两个数据集合对『结果』造成的影响没有时序行;

这种情况下,完全可以采用两个数据单独进行协程处理然后再进行后续运算;

// 伪代码
var result, data1, data2 int32
done1 := make(chan bool)
done2 := make(chan bool)
// 第一个数据集合,需要求和
go func() {
    for _, val := range dataset1 {
        data1 += val
    }
    done1 <- true
}

// 第二个数据集合,需要求和
go func() {
    for _, val := range dataset2 {
        data2 += val
    }
    done2 <- true
}

// 等待协程完成运算
<-done1
<-done2

// 结果进行相加
result = data1 + data2

2、需要额外进其他不相干的业务,不耽误『主协程』的返回值,不等待
一般有些业务处理以后,有些『额外工作』需要处理但是不耽误主协程返回数据,这个时候就可以开个协程去做,不用等待

// 伪代码

result, err := processMethod()
if err != nil {
    .....
}
// 需要对结果进行写缓存等其他操作,不耽误数据返回
go func() {
    err = saveRedis(result)
    if err != nil {
    	.....
	}
}

return result

3、对某些任务进行时间限制,『超时关闭』当前操作
例如,通过管道channel发送某些数据,若超时则自动放弃本次发送,关闭通道。

// 定义两个有缓冲通道,容量分别为1
c1 := make(chan string, 1) 
c2 := make(chan string, 1)

go func() {                     
    time.Sleep(time.Second * 1) // 隔1秒发送数据
    c1 <- "data1"        
}()

go func() {
    time.Sleep(time.Second * 6) // 隔6秒发送数据
    c2 <- "data2"             
}()

for i := 0; i < 2; i++ {    
    // 给通道创建容忍时间,如果5s内无法读写,就即刻返回
    tm := time.NewTimer(time.Second * 5) 
    // 使用select来获取这两个通道的值,然后输出
    select {
        case data1 := <-c1:          // 接收c1通道数据(消费数据)
        	fmt.Println(msg1)
        case data2 := <-c2:          // 接收c2通道数据(消费数据)
        	fmt.Println(msg2)
        case <-tm.C:
        	fmt.Println("timeout!")
    }
}

还等什么小编推荐自己的linuxC/C++语言交流群:【1106675687】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!前100名进群领取,额外赠送一份价值199的C/C++、linux资料包含(视频教程、电子书、实战项目及代码),下面部分展示。
在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简说Linux内核

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值