go并发编程(传统并发存在的问题/痛点;go就是要解决这些痛点)

func main() {
	var data int
	go func() {
		data++  //3行
	}()

	if data == 0{  //5行
		fmt.Printf("the value is %v.\n",data)  //6行
	}
}

// the value is 0. 【结果】
以上代码有三种可能的结果:
1。 不打印任何东西。在这种情况下,第3行执行,接下来第5行;
2。 打印"the value is 0"  . 第5行,6行在第3行之前执行;
3。 打印"the value is 1"  . 第5行执行,接下来,第3行,接下来,第6行

引入数据竞争的原因是因为开发人员在用顺序性的思维来思考问题。
分析可能存在的运行的多种结果:有时候想象在两个操作之间会经过很长一段时间;如调用goruntine的时间和它运行的时间相差1个小时。

有些情况,有些开发人员在他们的代码中使用了很多这种休眠语句time.Sleep。这种方式只能辅助调试。

结论是:应该始终以逻辑正确性为目标。在代码中引入休眠可以方便调试,但是这种并不是一种解决方案。
原子性:
某些程序/东西 是原子性的,这意味着在它运行的 环境中/上下文,它是不可分割的 或者不可中断的。

在你的进程上下文中进行原子操作,在操作系统的上下文中可能不是原子操作;在操作系统中原子操作,在机器环境中可能就不是原子的。
大多数语句不是原子的,更不用说函数,方法和程序了。

在考虑原子性时,经常第一件需要做的事就是 定义/确定 它的上下文,然后再考虑这些操作是否是原子性的。

分析例子:
i++
它经历三个步骤
1。 检索i的值
2。 增加i的值
3。 存储i的值

临界区:

func main() {
	var data int
	go func() {
		data++  //3行
	}()
	if data == 0{  //5行
		fmt.Printf("the value is %v.\n",data)  //6行
	}else{
		fmt.Printf("the value is %v.\n", data)
	}
}

/**
临界区:critical section
程序中需要独占访问共享资源的部分。
data++  正在增加数据变量。
if 语句,它检查数据的值是否为0
fmt.Printf语句, 在检索并输出数据的值。

 */

死锁:

type value struct {
	mu sync.Mutex
	value int
}

func main() {
	var wg sync.WaitGroup
	printSum := func(v1, v2 *value){
		defer wg.Done()
		v1.mu.Lock()
		defer v1.mu.Unlock()

		time.Sleep(2 * time.Second)
		v2.mu.Lock()
		defer v2.mu.Unlock()

		fmt.Printf("sum=%v\n", v1.value + v2.value)
	}

	var a, b value
	wg.Add(2)
	go printSum(&a, &b)
	go printSum(&b, &a)
	wg.Wait()
}
//fatal error: all goroutines are asleep - deadlock!
//运行时的图形表示。todo  图1-1

//出现死锁有几个必要条件/  Coffman 条件
/**
1。 相互排斥。;并发进程 拥有资源的独占权
2。 条件等待。 并发进程必须同时拥有一个资源,并等待额外的资源
3。 没有抢占。 并发进程拥有的资源只能被 该进程释放
4。 循环等待。
 */

活锁:

//活锁
func main() {
	cadence := sync.NewCond(&sync.Mutex{})
	go func() {
		for range time.Tick(1* time.Millisecond){
			cadence.Broadcast()
		}
	}()

	takeStep := func() {
		cadence.L.Lock()
		cadence.Wait()    //接收其他 goruntine 做完的信号
		cadence.L.Unlock()
	}

	//dirName 方向名称
	//dir 该方向所在人数
	//在该方向上只走一步,如果不能走,就退回原位
	tryDir := func(dirName string, dir *int32, out *bytes.Buffer) bool {
		fmt.Fprintf(out, "%v", dirName)
		atomic.AddInt32(dir, 1)   //原子加1 操作; 1毫秒时间足够保证两个人都执行了 加 1操作。
		takeStep()  //阻塞等待信号
		if atomic.LoadInt32(dir) == 1 {
			fmt.Fprint(out, ". Success!")
			return true
		}

		//没走成功; 等一个时间间隔,往相反方向走一步
		takeStep()
		atomic.AddInt32(dir , -1)  //原子减1操作
		return false
	}

	var left, right int32
	tryLeft := func(out *bytes.Buffer) bool {
		return tryDir("left", &left, out)
	}

	tryRight := func(out *bytes.Buffer) bool {
		return tryDir("right", &right, out)
	}

	walk := func(walking *sync.WaitGroup, name string) {
		var out bytes.Buffer
		defer func() {
			fmt.Println(out.String())
		}()
		defer walking.Done()
		fmt.Fprintf(&out, "%v is trying to scoot:", name)

		for i:=0; i<5; i++ {
			if tryLeft(&out) || tryRight(&out){
				return
			}
		}

		fmt.Fprintf(&out, "\n%v tosses her hands up in exasperation!", name)
	}

	var peopleInHallway sync.WaitGroup
	peopleInHallway.Add(2)
	go walk(&peopleInHallway, "Alice")
	go walk(&peopleInHallway, "Barbara")
	peopleInHallway.Wait()


}
活锁:

例子:
你走在走廊上 走向另一个人?她移动到一边让你通过,但是你也做了同样的事情。想象下这个情形永远持续走下去,这就是活锁。

出现活锁的原因:两个或两个以上的并发进程试图在没有协调的情况下防止死锁

活锁是 饥饿问题的子集

饥饿:

func main() {
	var wg sync.WaitGroup
	var sharedLock sync.Mutex
	const runtime = 1*time.Second

	//饥饿的goruntine
	greedyWorker := func() {
		defer wg.Done()
		var count int
		for begin := time.Now(); time.Since(begin) <= runtime; {
			sharedLock.Lock()
			time.Sleep(3*time.Nanosecond)
			sharedLock.Unlock()
			count++
		}
		fmt.Printf("Greedy worker was able to execute %v work loops\n", count)
	}

	politeWorker := func() {
		defer wg.Done()
		var count int
		for begin := time.Now(); time.Since(begin) <= runtime;{
			sharedLock.Lock()
			time.Sleep(1 *time.Nanosecond)
			sharedLock.Unlock()

			sharedLock.Lock()
			time.Sleep(1*time.Nanosecond)
			sharedLock.Unlock()

			sharedLock.Lock()
			time.Sleep(1*time.Nanosecond)
			sharedLock.Unlock()

			count++
		}
		fmt.Printf("Polite worker was able to excute %v work loops\n",count)
	}


	wg.Add(2)
	go greedyWorker()
	go politeWorker()

	wg.Wait()
}
//Greedy worker was able to execute 569243 work loops  【结果】
//Polite worker was able to excute 331233 work loops
在任何情况下,并发进程都无法获得执行工作所需的所有资源。

贪婪的worker 不必要地扩大其持有共享锁上的临界区,并阻止平和的worker 的 gorountine高效的工作。

识别饥饿 方法: metric: 通过记录来确定进程工作速度是否和你预期的一样高。

如果你使用了内存访问同步,你将不得不在粗粒度同步 和 细粒度同步之间找到一个平衡。

饥饿可以应用于 CPU, 内存, 文件句柄,数据连接;任何必须共享的资源都是饥饿的候选者。

如何确定并发安全:?

传统的情况:
如何利用并发编写代码,以及如何安全地使用代码并不总是那么明确。
注释可以带来一些改观。注释涵盖了这些内容:
谁负责并发?
如何利用并发原语解决这个问题?
谁负责同步?

Go语言处理并发的方式实际上可以帮助更清楚的表达问题域。
GO面对复杂性的简单性
运行时和通信困难并不是Go语言解决的,但使他们变得非常容易。

Go 的 低延GC, 很出色,从Go1.8开始,GC暂停一般在10-100μs 之间


Go语言的运行时 也会自动处理并发操作到操作系统线程上, 并可在线程间均匀映射。


需求:我想编写一个Web服务器,希望每个连接都可以被其他连接同时处理。
传统方式: 在某些语言中,在Web服务器开始接受连接之前,你可能必须创建一个线程集合(通常称为线程池), 然后将传入连接映射到线程。在每个线程
上,你需要循环该线程上的所有连接,以确保他们都获得了一些CPU时间。

并发和并行的区别:?

并发和并行的区别:
并发属于代码;并行属于一个运行中的程序
首先,我们并没有编写并行的代码,只有我们希望可以并行执行的并发代码,另外,并行是我们程序运行时的属性,而不是我们的代码
传统的:
Go语言之前,大部分主流编程语言都有一系列的抽象层。如果你想写并发代码,你需要对你的程序按照 线程以及对于内存访问之间使用 同步来建模。

Go语言并没有在 操作系统上增加了另外一层的抽象层, 我们取代了这些事物。线程依旧存在,但是我们发现几乎不再操作系统的线程层面来考虑我们的问题,
而是 在 goroutine 以及channel 的角度来思考问题,偶尔站在共享内存的角度来思考。

Go 语言并发原语的根基论文: Communicating Sequential Processes (通信顺序进程)
CSP 即 "Communicating Sequential Processes" (通信顺序进程):
 既是一个技术名词,也是介绍这种技术论文的名字。(发表于 1978)

 Hoare 可能应该使用 "函数" 这个词汇 代替 "Processes" 更合适。

 为了在进程之间通信,Hoare 创建了输入语输出的命令: !代表发送输入到一个进程;  ? 代表读取一个进程的输出。


 例子:需要构建一个端上请求字段的Web服务器,
 在一个仅提供线程抽象的语言中,很可能需要思考下面问题:
 1。 我的语言原生支持线程吗?还是我需要选择一个类库?
 2。 我的进程限制边界应该是什么?
 3。 线程在操作系统中的 权重?
 4。 我的程序需要运行的操作系统(各种操作系统如 windows linux等) 处理这些线程的时候有什么不同?
 5。 我需要创建一个工作线程池,该如何找到最佳的线程池大小?


 某个语言有一个可以把并行抽象出来的框架,但并不意味着这种自然的方式(go 方式)对并发问题建模并不重要,总有人必须写这些框架,而你的代码
 就需要编写在开发者 所构建的复杂的基础之上。复杂性在你编写代码的时候被隐藏了,并不意味着复杂性本身不存在,而复杂性正是滋生bug的温床。

 Go 语言的 select 语句使你可以高效的等待事件,从一个竞争的channel中随机地选择一个消息,并在没有消息时继续等待。

 Go语言的并发哲学
 追求简洁,尽量使用channel,并且认为gorountine的使用是没有成本的。
 使用通信来共享内存,而不是通过共享内存来通信。
 Go语言还支持通过内存访问同步 和 遵循该技术的原语 来编写并发代码的传统方式。sync 与其他包中的结构体与方法可以让你执行锁,创建资源池取代
 gorountine


todo  图2-1;

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值