Skip to content

Latest commit

 

History

History
208 lines (104 loc) · 16.9 KB

lock_futex.go.md

File metadata and controls

208 lines (104 loc) · 16.9 KB

File: lock_futex.go

lock_futex.go是Go语言运行时中实现锁机制的文件之一,其主要作用是提供与Linux系统Futex系统调用相关的函数实现。Futex(Fast Userspace Mutex)是一种可用于用户空间的轻量级锁机制,能够在多线程环境下保证共享资源的互斥访问。

lock_futex.go文件中主要实现了以下两个函数:

  1. func futex(addr *uint32, op int32, val uint32, timeout *timespec, addr2 *uint32, val3 uint32) (r1 uintptr, r2 uintptr, err error)

这个函数提供了对Futex系统调用封装的实现。Futex系统调用需要通过参数指定互斥锁的状态(等待或释放)以及等待超时时间,函数内部会将参数传递给系统调用,并检查返回值以判断是否成功。

  1. func futexwakeup(addr *uint32, cnt uint32)

这个函数用于唤醒等待互斥锁的线程。当一个线程调用futex等待互斥锁时,如果互斥锁当前被其他线程持有,则该线程会陷入阻塞等待状态。当互斥锁被释放时,持有它的线程可能会调用futexwakeup函数唤醒等待线程。

总之,lock_futex.go文件的主要作用是提供与Futex锁机制相关的函数实现,通过封装Futex系统调用,实现在Go语言中使用互斥锁的功能。

Functions:

key32

key32函数在lock_futex.go文件中的作用是生成一个32位的键值。该键值用于将线程的等待状态记录在内核中的等待队列中,以便在某个线程发出signal或broadcast信号时,被阻塞的线程可以被唤醒。

具体来说,key32函数使用了一个伪随机数生成器生成一个随机数,然后对该随机数进行进一步处理,得到一个32位的键值。该键值在内核中使用,用于表示某个锁的唯一标识符。

在多线程并发编程中,锁是一个重要的同步机制,可以保证多个线程在操作共享资源时的正确性。而使用内核级别的等待队列可以避免线程的忙等待,提高系统的效率。因此,在实现锁时需要使用到类似key32函数这样的功能,用于生成唯一的键值,以保证多线程操作的正确性和高效性。

lock

lock函数实现了互斥锁的加锁操作。互斥锁是一种用于线程同步的机制,它可以保证同时只有一个线程能够进入临界区(在临界区内的代码只有一个线程可以执行)。在go语言中,互斥锁是sync包中的一个重要组成部分,而lock_futex.go这个文件中的lock函数便是互斥锁的底层实现。

lock函数采用的是futex(Fast Userspace Mutex)的实现方式,它是一种用户空间的快速互斥锁实现方式。具体来说,它使用了操作系统提供的futex系统调用,在用户空间实现了锁的等待和唤醒机制。

当一个线程在进入临界区之前调用lock函数时,会首先尝试获取锁。如果锁已经被其他线程占用,那么当前线程会将自己加入到等待队列中,并且调用futex系统调用等待唤醒事件的发生。直到其他线程释放了锁并且唤醒了等待队列中的某个线程,当前线程才会从等待队列中被唤醒,然后重新尝试获取锁。

这种实现方式虽然可能存在一定的系统调用开销,但是它的优点在于能够快速地响应锁的释放并唤醒等待线程。此外,它还具有一定的自适应性,能够根据当前系统的负载动态地选择不同的等待策略,从而保证锁的性能表现。

lock2

lock2函数是Go语言运行时中的一个重要函数,它主要用于管理互斥锁和信号量的获取和释放操作,以保证多个线程或协程之间的同步访问。具体来说,lock2函数接受两个参数,第一个参数是一个指向mutex结构体的指针,第二个参数是一个整数值,表示需要获取或释放锁的类型。如果类型是1,表示需要获取互斥锁;如果类型是2,表示需要释放互斥锁;如果类型是3,表示需要获取信号量。该函数内部实现了对于三种类型的操作的逻辑处理,包括调用系统调用futex来实现等待锁或唤醒等待锁的线程,通过CAS操作来实现对mutex结构体的加锁和解锁,以及对于信号量的获取和释放等。总之,lock2函数在Go语言运行时中发挥着重要的作用,保证了Go程序的线程安全和同步访问。

unlock

unlock()函数在go/src/runtime/lock_futex.go文件中定义,用于释放由lock()函数获取的互斥锁。具体来说,当goroutine需要访问共享资源时,会先尝试获取互斥锁,如果该锁被其他goroutine持有,则当前goroutine会被挂起,直到该锁被释放。当资源使用完毕时,当前goroutine需要使用unlock()函数释放该锁,使得其他goroutine可以获取该锁。否则,其他goroutine将无法继续访问该共享资源,导致程序出现死锁等问题。

在具体实现上,unlock()函数首先会检查当前锁是否被持有,如果未被持有,则会抛出panic异常。接着,如果当前锁被持有,则会调用futexwakeup()函数唤醒等待该锁的goroutine,然后将锁状态设置为未持有。最后,unlock()函数会简单地调用atomic.StoreInt32()函数将mutex.flag字段设置为0,表示锁已被释放。

总结来说,unlock()函数就是用于释放互斥锁的。它采用原子操作确保锁的安全,唤醒等待该锁的goroutine,使得其他goroutine可以获取该锁,从而实现并发访问共享资源的线程安全。

unlock2

unlock2函数是Go语言运行时库中的一个函数,其作用是调用系统的futex函数将锁的状态从locked变为unlocked。它是在lock_futex.go文件中定义的,该文件实现了基于futex系统调用的同步原语,包括锁和条件变量。

在Go语言中,锁用于同步共享资源的访问,以避免多个线程同时访问同一资源导致的竞争条件问题。当一个线程获得了锁并进入临界区时,其他线程必须等待锁被释放才能访问临界区。unlock2函数就是在临界区结束后将锁释放的函数。

在unlock2函数中,它会首先获取锁对象的地址,然后通过atomic.Cas()函数将锁的状态从locked变为unlocked。如果原来锁的状态确实是locked,则该函数返回true,否则返回false。如果返回true,则说明该线程是第一个释放该锁的线程,需要调用futex函数唤醒其他线程;如果返回false,则说明其他线程已经在竞争锁,不需要再唤醒其他线程。

当一个线程等待锁时,它会通过futex函数挂起等待锁的状态从locked变为unlocked。当一个线程释放了锁时,它就需要调用futex函数唤醒其他线程。这样可以避免忙等待的问题,减少线程的切换和CPU的资源浪费,提高程序的性能。

noteclear

在Go的运行时系统中,goroutines之间的通信通常是通过channel进行的。当一个goroutine需要等待channel中的某个操作时,它会进入channel的等待队列。等待队列中的每个元素都与一个等待锁相关联,这个等待锁由sync.Mutex类型的字段控制。

在Linux系统中,Go运行时系统使用了futex来实现等待锁。Futex(Fast Userspace Mutex)是一个基于用户空间的锁机制,它利用了Linux内核对等待异步事件的支持,可以在不进行系统调用的情况下,更快地锁定和等待锁。

在lock_futex.go文件中,noteclear函数用于清空等待队列元素的等待状态标记。当等待锁已经解除时,noteclear将重新初始化等待队列元素,并取消它们的等待状态,以便它们可以再次被重用。此操作可以避免在使用锁时出现一些问题,例如死锁和资源耗尽。

总之,noteclear函数是Go运行时系统中实现futex锁的一部分,它确保锁在正确的时刻释放,并避免了某些常见的并发问题。

notewakeup

notewakeup函数是一个系统调用,用于唤醒阻塞在通知对象上的goroutine。在实现上,它执行以下步骤:

  1. 尝试从通知对象的等待列表中获取一个等待的goroutine;
  2. 如果找到了等待的goroutine,将其从等待列表中移除,并将其添加到运行队列中;
  3. 如果没有找到等待的goroutine,什么也不做。

通知对象是锁的一部分,用于实现goroutine的同步和互斥。通知对象可以有两种状态:未触发和已触发。当通知对象未触发时,所有等待该通知对象的goroutine都被阻塞。当通知对象被触发后,它将唤醒其中一个等待的goroutine,并将其从阻塞状态转换为运行状态。

notewakeup函数在实现锁的时候有很重要的作用。在lock_futex.go文件中,notewakeup函数主要被用于两种情况:

  1. 唤醒阻塞在互斥锁上的goroutine。当一个goroutine尝试获取互斥锁但无法获取时,它将会被添加到互斥锁的等待列表中,并将其阻塞。当其他goroutine释放互斥锁时,notewakeup函数将被调用,以便将其中一个等待的goroutine从等待列表中唤醒。

  2. 唤醒阻塞在条件变量上的goroutine。条件变量与互斥锁配合使用,用于在goroutine之间进行状态同步。当goroutine在条件变量上等待时,它将被阻塞,并从互斥锁的等待列表中移除。当条件变量触发时,notewakeup函数将被调用,以便将其中一个等待的goroutine从条件变量的等待列表中唤醒。

总之,notewakeup函数的作用是唤醒阻塞在通知对象上的goroutine,并将其添加到运行队列中。它在锁的实现中起着重要的作用,确保goroutine之间的状态同步和互斥。

notesleep

函数notesleep是锁的等待函数,在Go语言的调度器中,每个线程都拥有一个调度器和一个Goroutine队列。当某个线程需要获取锁时,如果目前锁已被其他线程持有,则当前线程会将自己的Goroutine放到等待队列中,然后进入睡眠状态。

具体来说,notesleep的作用是将当前线程加入等待队列,并且让线程进入睡眠状态(或者更准确地说是让线程阻塞)。当锁释放时,等待队列中的线程会被唤醒,然后重新进行调度。

notesleep主要由以下几个步骤构成:

  1. 首先检查当前线程是否被取消,如果是,则陷入错误状态;
  2. 将当前线程添加到等待队列中;
  3. 尝试获取锁,如果当前线程成功获取到锁,则说明不需要等待,直接返回;
  4. 否则,使用底层系统调用futex将当前线程阻塞,并等待锁被释放。

总的来说,notesleep是锁等待函数中最核心的部分,它确保了线程在等待锁的时候能够释放CPU资源,不会占用过多的系统资源。

notetsleep_internal

lock_futex.go是Go语言运行时中负责底层同步原语的文件之一。notetsleep_internal是其中一个函数,其在runtime包中被多个方法使用。它的作用是在指定睡眠时间内让当前goroutine处于休眠状态,直到被唤醒或者超时。

notetsleep_internal函数的具体实现通过调用系统API futex()和nanotime()完成。在函数中,它首先通过调用nanotime()获取当前时间,然后计算出相对于当前时间的绝对时间。接着,它使用futex()等待在一个锁上,直到被唤醒或者超时。当函数被唤醒时,它会检查是否已经超时,并更新调用者提供的deadline参数。如果已经超时,则返回TIMEOUT,否则返回OK。

notetsleep_internal在runtime包的很多方法中都被频繁使用,例如:semacquire函数、notetsleep函数等。这些方法都需要在某个时刻等待某个事件的发生,而notetsleep_internal的实现能够提供非常高效的等待机制,同时减少了等待时的系统负载。

notetsleep

notetsleep函数的作用是在一个无锁状态的条件变量上等待信号的到来。

首先,notetsleep函数会获取一个mutex锁,然后会检查notetsleep的状态(目前是否在等待中)。如果当前不在等待中,函数会将notetsleep状态设置为等待中,并将锁释放,然后等待在条件变量上。

当收到信号并且notetsleep处于等待状态时,它会重新获取mutex锁并检查notetsleep的状态。如果状态为等待中,它会将状态设置为未等待中,并返回,然后释放锁。

如果收到的信号时notetsleep没有处于等待状态,那么信号将被忽略。这种情况下,notetsleep将释放锁并立即返回。在发布者不知道是否有等待者或者没有关心是否所有等待者都已被唤醒的情况下,这种情况是十分普遍的。

总之,notetsleep函数提供了一种协调多个goroutine之间的同步机制,可以在必要时通过条件变量来等待某个事件的发生。它是golang运行时中非常常见的一个重要函数。

notetsleepg

notetsleepg是Goroutine中的一种同步机制,它的作用是在等待某种条件满足时暂停当前的Goroutine,直到被唤醒。在lock_futex.go中,它用于实现锁和条件变量的等待和通知机制。notetsleepg的定义如下:

func notetsleepg(n *note, ns int64) int {
	gp := getg()

	if atomic.Load8(&gp.m.blocked) != 0 {  // 如果当前G不是阻塞状态,就设置成阻塞状态
		throw("notetsleepg - blocked on entry")
	}

	if n == &note{wait: 0} {  // 如果note的wait值为0,就将其设置为1,代表等待被唤醒
		throw("notetsleepg - zero note")
	}
	n.wait = 1

	// 将当前G添加到等待队列
	gp.m.blocked = 1
	addnote(n)
	unlock(&futex0)  // 解锁锁
	gopark(&futex0, "Select", traceEvGoBlockSelect, 2)

	// 当G被唤醒时,重新竞争锁
	if gp.m.locks != 0 || gp.m.mallocing != 0 {
		throw("notetsleepg - deadlock")
	}
	lock(&futex0)
	if n.wait != 0 {
		return -1  // 如果等待标志仍然为1,说明未被正确唤醒,返回-1
	}
	return 0  // 如果等待标志已被清除,说明已成功唤醒,返回0
}

notetsleepg函数会首先将当前的Goroutine设置为阻塞状态,并将它添加到note对象的等待队列中,然后调用gopark函数将当前Goroutine挂起,等待被唤醒。在唤醒时,notetsleepg将统计锁的情况并重新竞争锁,如果等待标志已被清除,说明已成功唤醒,返回0;否则,未被正确唤醒,返回-1。

在Go的标准库中,notetsleepg被广泛地用于实现锁和条件变量等同步机制。例如,在sync包的Mutex和Cond中都使用了notetsleepg函数。Mutex的Lock函数会调用notetsleepg等待锁,Unlock函数会调用noteclear函数,通知等待锁的Goroutine;而Cond的Wait函数会调用notetsleepg等待条件变量,Signal和Broadcast函数会调用notewakeup函数,通知等待条件变量的Goroutine。

beforeIdle

在Go的运行时(runtime)中,Lock Futex是一种用于同步机制的锁。lock_futex.go是一个与Lock Futex锁相关的Go源文件,该文件中定义了包含beforeIdle函数的类型spinLock。beforeIdle方法是一种协程(goroutine)在等待锁时运行的函数。

在Lock Futex锁中,当前的协程需要等待锁在内核中释放,以便它可以获得对锁的访问权限。在等待期间,协程需要暂停,并告诉Lock Futex锁当前的状态。beforeIdle函数就是在这个时候运行的。

beforeIdle在协程等待锁的时候运行,它的作用有两个方面:

  1. 它告诉Lock Futex锁当前协程的状态。在等待期间,当前协程可能会有其他事情需要做(例如,等待计时器或网络I/O),因此它需要告诉Lock Futex它仍在等待锁,并在后台执行其他任务。

  2. 它设置了休眠的条件,并将当前协程设置为休眠状态。在锁释放之前,锁必须记住所有等待它的协程,并在锁可用时唤醒它们。beforeIdle函数告诉Lock Futex如何休眠当前协程,并在锁释放时再次唤醒它。

checkTimeouts

checkTimeouts是一个用于检查互斥锁等待超时的函数。在并发编程中,互斥锁的目的是为了保护共享资源的访问。如果多个goroutine尝试同时访问这些资源,那么就需要使用互斥锁来保护它们。当一个goroutine请求一个已经被其他goroutine锁定的互斥锁时,它将被阻塞,并等待锁被释放。但是如果锁持有者在锁住互斥锁后不释放它,或者由于任何其他原因导致被阻塞的goroutine不能获取锁,则会发生死锁。

为了防止死锁,go语言在运行时使用了超时机制。当goroutine请求锁时,它会设置一个超时时间,并且如果在超时时间到期之前锁没有被释放,则该goroutine将放弃等待并返回一个错误。

checkTimeouts函数的主要作用是遍历当前等待互斥锁的所有goroutine,并检查它们是否已经超时。如果某个goroutine超时了,那么它将被强制唤醒并返回一个错误。这可以防止死锁,并确保goroutine不会无限期地阻塞。

在实现中,checkTimeouts调用了go的低级别系统调用,如nanosleep和futex以及一些互斥锁的内部函数来实现它的功能。它是go语言运行时中一个非常重要的模块,确保了go程序在并发环境下的稳定性和可靠性。