Linux下实现高效多核并发编程技巧 (linux 多核并发编程)

随着计算机硬件的不断发展,多核处理器已成为主流,因而多核并发编程的重要性也得到了进一步的提升。对于Linux系统而言,其作为一个以多用户、多任务为特点的操作系统,自然而然地也需要支持多核并发编程。本文就是基于这一背景,介绍一些Linux下实现高效多核并发编程的技巧。

1、线程模型

在Linux下实现高效多核并发编程的之一步,就是选择适当的线程模型。线程模型是指用于实现并发编程的线程库和编程模型。目前常见的线程模型有两种,分别为用户空间线程和内核空间线程。

用户空间线程是指基于用户空间的线程库,它的优势在于创建线程的开销较小,但其缺陷在于不能利用操作系统的多核优势,因为它不能绑定到处理器核心上,也就不能实现多核并发。相比之下,内核空间线程则是基于操作系统内核的线程库,它的优势在于可以实现真正的多核并发,但其缺陷在于创建线程的开销较大。

因此,在选择线程模型时,需要根据实际需求来进行选择。如果程序中线程数较少,可以使用用户空间线程;而如果线程数较多,需要实现真正的多核并发,就应选择内核空间线程。

2、任务调度

在Linux系统中,任务调度是实现多核并发的基础。任务调度是指操作系统将处理器核心分配给不同的任务或线程的过程。Linux操作系统是用时间片轮转算法来实现任务调度的,在多核处理器下,每个处理器核心都会运行一个调度器,因此,需要实现良好的任务调度策略,才能充分利用多核。

一种常见的任务调度策略是将任务分配到不同的处理器核心上,以实现真正的并发。在Linux系统中,可以使用taskset命令来设置任务的CPU亲和性,从而将其绑定到指定的处理器核心上。这样,不同的任务会被分配到不同的处理器核心上运行,从而实现真正的并发。

除了将任务分配到不同的处理器核心上,还可以使用加锁和降低线程优先级等策略来防止多核并发下的数据竞争和资源争夺,从而提高程序的并发性。

3、优化程序

为了进一步提高多核并发程序的效率,需要对程序进行优化。常见的程序优化方法包括分离任务和数据,使用异步IO等。

分离任务和数据是指将任务和数据分别存储在不同的内存中,从而避免多线程同时访问相同的内存区域,造成资源争夺和竞争。使用异步IO则是指在提高I/O性能的同时,避免对线程的阻塞。

此外,还可以使用OpenMP等并行编程框架来加速程序,提高并发性能。例如,使用OpenMP可以将循环语句并行化,从而充分利用多核处理器的优势,提高程序运行速度。

4、性能分析和调试

在实现多核并发编程时,还需要进行性能分析和调试,以优化程序性能和解决问题。常用的性能分析工具包括perf、gprof、valgrind等,它们可以帮助开发者快速定位程序性能瓶颈和问题所在。在进行性能分析时,需要注意同时监控CPU使用率、内存使用率、IO使用率等指标。

Linux下实现高效多核并发编程需要综合考虑线程模型、任务调度、程序优化、性能分析等多个方面,以充分利用多核处理器的优势,实现高效的并发编程。作为开发者,需要了解并掌握这些技巧,以提高程序性能和开发效率。

相关问题拓展阅读:

linux tcp socket并发编程,调用accept函数后调用fork。两台电脑测试,为何accept返回的描述符是相同的?

返回的描述符是当前可用的最小描述符,你测试环境下的两台电脑可用的最小描述符是一样的。

可能是你的fork 之后产生的子进程拷贝的代码,它那里执行了accept,所以导致相迹腊拦同,你试着在每个fork之后的函数中用个exit(1);将子进程退出,然后应该就不会有这个问姿胡题了局坦。

如何实现linux下多线程之间的互斥与同步

Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导致竞态,linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景。

Linux内核是多进程、多线程的操作系统,它提供了相当完整的内核同步方法。内核同步方法列表如下:

中断屏蔽

原子操作

自旋锁

读写自旋锁

顺序锁

信号量

读写信号量

BKL(大内核锁)

Seq锁

一、并发与竞态:

定义:

并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。

在linux中,主要的竞态发生在如下几种情况:

1、对称多处理器历辩埋(P)多个CPU

特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。

2、单CPU内进程与抢占它的进程

3、中断(硬中断、软中断、Tasklet、底半部)与进程之间

只要并发的多个执行单元存在对共享资源的访问,竞态就有可能发生。

如果中断处理程序访问进程正在访问的资源,则竞态也会会发生。

多个中断之间本身也可能引起并发而导致竞态(中断被更高优先级的中断打断)。

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元在访问共享资源的时候,其他的执行单元都被禁止访问。

访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护,中断屏蔽,原子操作,自旋锁,和信号量都是linux设备驱动中可采用的互斥途径。

临界区和竞争条件:

所谓临界区(critical regions)就是访问和操作共享数据的代码段,为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行——也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样,如果两个执行线程有可能处于同一个临界区中,那么就是程序包含一个bug,如果这种情况发生了,我们就称之为竞争条件(race conditions),避免并发和防止竞争条件被称为同步。

死锁:

死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了,所有线程都在相互等待,但它们永远不会释放已经占有的资源,于是任何线程都无法继续,这便意味着死锁的发生。

二、中断屏蔽

在单CPU范围内避免竞态的一种简单方法是在进入临界区之前屏蔽系统的中断。

由于linux内核的进程调度等操作都依赖中断来实现,内核抢占进程之间的并发也就得以避免了。

中断屏蔽的使用方法:

local_irq_disable()//屏蔽中断

//临界区

local_irq_enable()//灶念开中断

特点:

由于linux系统的异步IO,进程调度等很多重要操作都依赖于中断,在屏蔽中断期间所有的中断都无法得到处理,因此长时间的屏蔽是很危险的,有可能造成数据丢失甚至系统崩溃,这就要求在屏蔽中断之后,当前的内核执行路径应当尽快地执行完临界区的代码。

中断屏蔽只能禁止本CPU内的中断,因此,并不能解决多CPU引发的竞态,所以单独使用中断屏蔽并不是一个值得推荐的避免竞态的方法,它一般和自旋锁配合使用。

三、原子操作

定义:原子操作指的是在执行过程中不会被别的代码路径所中断的操作。

(原子原本指的是不可分割的微粒,所以原子操作也就是不能够被分割的指令)

(它保证指令以“原子”的方式执行而不能被打断)

原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是” 原子操作”,因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。但是,在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以decl (递减指令)为例,这是一个典型的”读-改-肢蚂写”过程,涉及两次内存访问。

通俗理解:

原子操作,顾名思义,就是说像原子一样不可再细分。一个操作是原子操作,意思就是说这个操作是以原子的方式被执行,要一口气执行完,执行过程不能够被OS的其他行为打断,是一个整体的过程,在其执行过程中,OS的其它行为是插不进来的。

分类:linux内核提供了一系列函数来实现内核中的原子操作,分为整型原子操作和位原子操作,共同点是:在任何情况下操作都是原子的,内核代码可以安全的调用它们而不被打断。

原子整数操作:

针对整数的原子操作只能对atomic_t类型的数据进行处理,在这里之所以引入了一个特殊的数据类型,而没有直接使用C语言的int型,主要是出于两个原因:

之一、让原子函数只接受atomic_t类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用,同时,这也确保了该类型的数据不会被传递给其它任何非原子函数;

第二、使用atomic_t类型确保编译器不对相应的值进行访问优化——这点使得原子操作最终接收到正确的内存地址,而不是一个别名,最后就是在不同体系结构上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。

原子整数操作最常见的用途就是实现计数器。

另一点需要说明原子操作只能保证操作是原子的,要么完成,要么不完成,不会有操作一半的可能,但原子操作并不能保证操作的顺序性,即它不能保证两个操作是按某个顺序完成的。如果要保证原子操作的顺序性,请使用内存屏障指令。

atomic_t和ATOMIC_INIT(i)定义

typedef struct { volatile int counter; } atomic_t;

#define ATOMIC_INIT(i) { (i) }

在你编写代码的时候,能使用原子操作的时候,就尽量不要使用复杂的加锁机制,对多数体系结构来讲,原子操作与更复杂的同步方法相比较,给系统带来的开销小,对高速缓存行的影响也小,但是,对于那些有高性能要求的代码,对多种同步方法进行测试比较,不失为一种明智的作法。

原子位操作:

针对位这一级数据进行操作的函数,是对普通的内存地址进行操作的。它的参数是一个指针和一个位号。

为方便其间,内核还提供了一组与上述操作对应的非原子位函数,非原子位函数与原子位函数的操作完全相同,但是,前者不保证原子性,且其名字前缀多两个下划线。例如,与test_bit()对应的非原子形式是_test_bit(),如果你不需要原子性操作(比如,如果你已经用锁保护了自己的数据),那么这些非原子的位函数相比原子的位函数可能会执行得更快些。

四、自旋锁

自旋锁的引入:

如 果每个临界区都能像增加变量这样简单就好了,可惜现实不是这样,而是临界区可以跨越多个函数,例如:先得从一个数据结果中移出数据,对其进行格式转换和解 析,最后再把它加入到另一个数据结构中,整个执行过程必须是原子的,在数据被更新完毕之前,不能有其他代码读取这些数据,显然,简单的原子操作是无能为力 的(在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是” 原子操作”,因为中断只能发生于指令之间),这就需要使用更为复杂的同步方法——锁来提供保护。

自旋锁的介绍:

Linux内核中最常见的锁是自旋锁(spin lock),自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图获得一个被争用(已经被持有)的自旋锁,那么该线程就会一直进行忙循环—旋转—等待锁重新可用,要是锁未被争用,请求锁的执行线程便能立刻得到它,继续执行,在任意时间,自旋锁都可以防止多于一个的执行线程同时进入理解区,注意同一个锁可以用在多个位置—例如,对于给定数据的所有访问都可以得到保护和同步。

一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),所以自旋锁不应该被长时间持有,事实上,这点正是使用自旋锁的初衷,在短期间内进行轻量级加锁,还可以采取另外的方式来处理对锁的争用:让请求线程睡眠,直到锁重新可用时再唤醒它,这样处理器就不必循环等待,可以去执行其他代码,这也会带来一定的开销——这里有两次明显的上下文切换, 被阻塞的线程要换出和换入。因此,持有自旋锁的时间更好小于完成两次上下文切换的耗时,当然我们大多数人不会无聊到去测量上下文切换的耗时,所以我们让持 有自旋锁的时间应尽可能的短就可以了,信号量可以提供上述第二种机制,它使得在发生争用时,等待的线程能投入睡眠,而不是旋转。

自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它们会导致睡眠),在中断处理程序中使用自旋锁时,一定要在获取锁之前,首先禁止本地中断(在 当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争用这个已经持有的自旋锁,这样以来,中断处理程序就会自旋, 等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行,这正是我们在前一章节中提到的双重请求死锁,注意,需要关闭的只是当前处理器上的中断,如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。

自旋锁的简单理解:

理解自旋锁最简单的方法是把它作为一个变量看待,该变量把一个临界区或者标记为“我当前正在运行,请稍等一会”或者标记为“我当前不在运行,可以被使用”。如果A执行单元首先进入例程,它将持有自旋锁,当B执行单元试图进入同一个例程时,将获知自旋锁已被持有,需等到A执行单元释放后才能进入。

自旋锁的API函数:

其实介绍的几种信号量和互斥机制,其底层源码都是使用自旋锁,可以理解为自旋锁的再包装。所以从这里就可以理解为什么自旋锁通常可以提供比信号量更高的性能。

自旋锁是一个互斥设备,他只能会两个值:“锁定”和“解锁”。它通常实现为某个整数之中的单个位。

“测试并设置”的操作必须以原子方式完成。

任何时候,只要内核代码拥有自旋锁,在相关CPU上的抢占就会被禁止。

适用于自旋锁的核心规则:

(1)任何拥有自旋锁的代码都必须使原子的,除服务中断外(某些情况下也不能放弃CPU,如中断服务也要获得自旋锁。为了避免这种锁陷阱,需要在拥有自旋锁时禁止中断),不能放弃CPU(如休眠,休眠可发生在许多无法预期的地方)。否则CPU将有可能永远自旋下去(死机)。

(2)拥有自旋锁的时间越短越好。

需 要强调的是,自旋锁别设计用于多处理器的同步机制,对于单处理器(对于单处理器并且不可抢占的内核来说,自旋锁什么也不作),内核在编译时不会引入自旋锁 机制,对于可抢占的内核,它仅仅被用于设置内核的抢占机制是否开启的一个开关,也就是说加锁和解锁实际变成了禁止或开启内核抢占功能。如果内核不支持抢 占,那么自旋锁根本就不会编译到内核中。

内核中使用spinlock_t类型来表示自旋锁,它定义在:

typedef struct {

raw_spinlock_t raw_lock;

#if defined(CONFIG_PREEMPT) && defined(CONFIG_P)

unsigned int break_lock;

#endif

} spinlock_t;

对于不支持P的内核来说,struct raw_spinlock_t什么也没有,是一个空结构。对于支持多处理器的内核来说,struct raw_spinlock_t定义为

typedef struct {

unsigned int slock;

} raw_spinlock_t;

slock表示了自旋锁的状态,“1”表示自旋锁处于解锁状态(UNLOCK),“0”表示自旋锁处于上锁状态(LOCKED)。

break_lock表示当前是否由进程在等待自旋锁,显然,它只有在支持抢占的P内核上才起作用。

自旋锁的实现是一个复杂的过程,说它复杂不是因为需要多少代码或逻辑来实现它,其实它的实现代码很少。自旋锁的实现跟体系结构关系密切,核心代码基本也是由汇编语言写成,与体协结构相关的核心代码都放在相关的目录下,比如。对于我们驱动程序开发人员来说,我们没有必要了解这么spinlock的内部细节,如果你对它感兴趣,请参考阅读Linux内核源代码。对于我们驱动的spinlock接口,我们只需包括头文件。在我们详细的介绍spinlock的API之前,我们先来看看自旋锁的一个基本使用格式:

#include

spinlock_t lock = SPIN_LOCK_UNLOCKED;

spin_lock(&lock);

….

spin_unlock(&lock);

从使用上来说,spinlock的API还很简单的,一般我们会用的的API如下表,其实它们都是定义在中的宏接口,真正的实现在中

#include

SPIN_LOCK_UNLOCKED

DEFINE_SPINLOCK

spin_lock_init( spinlock_t *)

spin_lock(spinlock_t *)

spin_unlock(spinlock_t *)

spin_lock_irq(spinlock_t *)

spin_unlock_irq(spinlock_t *)

spin_lock_irqsace(spinlock_t *,unsigned long flags)

spin_unlock_irqsace(spinlock_t *, unsigned long flags)

spin_trylock(spinlock_t *)

spin_is_locked(spinlock_t *)

• 初始化

spinlock有两种初始化形式,一种是静态初始化,一种是动态初始化。对于静态的spinlock对象,我们用 SPIN_LOCK_UNLOCKED来初始化,它是一个宏。当然,我们也可以把声明spinlock和初始化它放在一起做,这就是 DEFINE_SPINLOCK宏的工作,因此,下面的两行代码是等价的。

DEFINE_SPINLOCK (lock);

spinlock_t lock = SPIN_LOCK_UNLOCKED;

spin_lock_init 函数一般用来初始化动态创建的spinlock_t对象,它的参数是一个指向spinlock_t对象的指针。当然,它也可以初始化一个静态的没有初始化的spinlock_t对象。

spinlock_t *lock

……

spin_lock_init(lock);

• 获取锁

内核提供了三个函数用于获取一个自旋锁。

spin_lock:获取指定的自旋锁。

spin_lock_irq:禁止本地中断并获取自旋锁。

spin_lock_irqsace:保存本地中断状态,禁止本地中断并获取自旋锁,返回本地中断状态。

自旋锁是可以使用在中断处理程序中的,这时需要使用具有关闭本地中断功能的函数,我们推荐使用 spin_lock_irqsave,因为它会保存加锁前的中断标志,这样就会正确恢复解锁时的中断标志。如果spin_lock_irq在加锁时中断是关闭的,那么在解锁时就会错误的开启中断。

另外两个同自旋锁获取相关的函数是:

spin_trylock():尝试获取自旋锁,如果获取失败则立即返回非0值,否则返回0。

spin_is_locked():判断指定的自旋锁是否已经被获取了。如果是则返回非0,否则,返回0。

• 释放锁

同获取锁相对应,内核提供了三个相对的函数来释放自旋锁。

spin_unlock:释放指定的自旋锁。

spin_unlock_irq:释放自旋锁并激活本地中断。

spin_unlock_irqsave:释放自旋锁,并恢复保存的本地中断状态。

五、读写自旋锁

如 果临界区保护的数据是可读可写的,那么只要没有写操作,对于读是可以支持并发操作的。对于这种只要求写操作是互斥的需求,如果还是使用自旋锁显然是无法满 足这个要求(对于读操作实在是太浪费了)。为此内核提供了另一种锁-读写自旋锁,读自旋锁也叫共享自旋锁,写自旋锁也叫排他自旋锁。

读写自旋锁是一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念,但是在写操作方面,只能最多有一个写进程,在读操作方面,同时可以有多个读执行单元,当然,读和写也不能同时进行。

读写自旋锁的使用也普通自旋锁的使用很类似,首先要初始化读写自旋锁对象:

// 静态初始化

rwlock_t rwlock = RW_LOCK_UNLOCKED;

//动态初始化

rwlock_t *rwlock;

rw_lock_init(rwlock);

在读操作代码里对共享数据获取读自旋锁:

read_lock(&rwlock);

read_unlock(&rwlock);

在写操作代码里为共享数据获取写自旋锁:

write_lock(&rwlock);

write_unlock(&rwlock);

需要注意的是,如果有大量的写操作,会使写操作自旋在写自旋锁上而处于写饥饿状态(等待读自旋锁的全部释放),因为读自旋锁会自由的获取读自旋锁。

读写自旋锁的函数类似于普通自旋锁,这里就不一一介绍了,我们把它列在下面的表中。

RW_LOCK_UNLOCKED

rw_lock_init(rwlock_t *)

read_lock(rwlock_t *)

read_unlock(rwlock_t *)

read_lock_irq(rwlock_t *)

read_unlock_irq(rwlock_t *)

read_lock_irqsave(rwlock_t *, unsigned long)

read_unlock_irqsave(rwlock_t *, unsigned long)

write_lock(rwlock_t *)

write_unlock(rwlock_t *)

write_lock_irq(rwlock_t *)

write_unlock_irq(rwlock_t *)

write_lock_irqsave(rwlock_t *, unsigned long)

write_unlock_irqsave(rwlock_t *, unsigned long)

rw_is_locked(rwlock_t *)

六、顺序琐

顺序琐(seqlock)是对读写锁的一种优化,若使用顺序琐,读执行单元绝不会被写执行单元阻塞,也就是说,读执行单元可以在写执行单元对被顺序琐保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才去进行写操作。

但是,写执行单元与写执行单元之间仍然是互斥的,即如果有写执行单元在进行写操作,其它写执行单元必须自旋在哪里,直到写执行单元释放了顺序琐。

如果读执行单元在读操作期间,写执行单元已经发生了写操作,那么,读执行单元必须重新读取数据,以便确保得到的数据是完整的,这种锁在读写同时进行的概率比较小时,性能是非常好的,而且它允许读写同时进行,因而更大的提高了并发性,

注意,顺序琐由一个限制,就是它必须被保护的共享资源不含有指针,因为写执行单元可能使得指针失效,但读执行单元如果正要访问该指针,将导致Oops。

七、信号量

Linux中的信号量是一种睡眠锁,如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠,这时处理器能重获自由,从而去执行其它代码,当持有信号量的进程将信号量释放后,处于等待队列中的哪个任务被唤醒,并获得该信号量。

信号量,或旗标,就是我们在操作系统里学习的经典的P/V原语操作。

P:如果信号量值大于0,则递减信号量的值,程序继续执行,否则,睡眠等待信号量大于0。

V:递增信号量的值,如果递增的信号量的值大于0,则唤醒等待的进程。

信号量的值确定了同时可以有多少个进程可以同时进入临界区,如果信号量的初始值始1,这信号量就是互斥信号量(MUTEX)。对于大于1的非0值信号量,也可称为计数信号量(counting semaphore)。对于一般的驱动程序使用的信号量都是互斥信号量。

类似于自旋锁,信号量的实现也与体系结构密切相关,具体的实现定义在头文件中,对于x86_32系统来说,它的定义如下:

struct semaphore {

atomic_t count;

int sleepers;

wait_queue_head_t wait;

};

信号量的初始值count是atomic_t类型的,这是一个原子操作类型,它也是一个内核同步技术,可见信号量是基于原子操作的。我们会在后面原子操作部分对原子操作做详细介绍。

信号量的使用类似于自旋锁,包括创建、获取和释放。我们还是来先展示信号量的基本使用形式:

static DECLARE_MUTEX(my_sem);

……

if (down_interruptible(&my_sem))

{

return -ERESTARTSYS;

}

……

up(&my_sem)

Linux内核中的信号量函数接口如下:

static DECLARE_SEMAPHORE_GENERIC(name, count);

static DECLARE_MUTEX(name);

seam_init(struct semaphore *, int);

init_MUTEX(struct semaphore *);

init_MUTEX_LOCKED(struct semaphore *)

down_interruptible(struct semaphore *);

down(struct semaphore *)

down_trylock(struct semaphore *)

up(struct semaphore *)

• 初始化信号量

信号量的初始化包括静态初始化和动态初始化。静态初始化用于静态的声明并初始化信号量。

static DECLARE_SEMAPHORE_GENERIC(name, count);

static DECLARE_MUTEX(name);

对于动态声明或创建的信号量,可以使用如下函数进行初始化:

seam_init(sem, count);

init_MUTEX(sem);

init_MUTEX_LOCKED(struct semaphore *)

显然,带有MUTEX的函数始初始化互斥信号量。LOCKED则初始化信号量为锁状态。

• 使用信号量

信号量初始化完成后我们就可以使用它了

down_interruptible(struct semaphore *);

down(struct semaphore *)

down_trylock(struct semaphore *)

up(struct semaphore *)

down函数会尝试获取指定的信号量,如果信号量已经被使用了,则进程进入不可中断的睡眠状态。down_interruptible则会使进程进入可中断的睡眠状态。关于进程状态的详细细节,我们在内核的进程管理里在做详细介绍。

down_trylock尝试获取信号量, 如果获取成功则返回0,失败则会立即返回非0。

当退出临界区时使用up函数释放信号量,如果信号量上的睡眠队列不为空,则唤醒其中一个等待进程。

八、读写信号量

类似于自旋锁,信号量也有读写信号量。读写信号量API定义在头文件中,它的定义其实也是体系结构相关的,因此具体实现定义在头文件中,以下是x86的例子:

struct rw_semaphore {

signed long count;

spinlock_t wait_lock;

struct list_head wait_list;

};

Linux开发需要什么呢??需要具备什么基础呢??

linux更先要学的是Linux基础知识,学完基础知识才算入门,之后还要学习综合架构、Shell编程、数据库、云计算以及网络安全方面的知识,以下是linux基础部分要学习的内容:

1. 计算机硬件、组成原理、操作系统基础、Linux起源、核心介绍及Linux安装实战入门

2. Xshell远程网络连接Linux、基础优化、远程连接网络基础纳枝烂、Xshell连接故障排错、核心基础命令讲解

3. Linux系统核心通配符体系、三剑客(grep,sed,awk)核搭贺心正则表达式精讲及企业级案例实战模拟精讲

4. Bash核心符号、快捷键、通配符详解

5. Linux目录、FHS\挂载、文件属性、核心洞漏目录精讲

6. Linux文件及目录管理核心知识和命令精讲(第二关)

7. Linux企业级基础优化(工作中可直接使用

8. Linux文件及目录权限精讲及多个企业案例模拟

9. Linux重要核心命令回顾与深入精讲(第三关)

一、linux和os:

1、命令:

netstat tcpdump ipcs ipcrm 这四个命令的熟练掌握程度基本上能体现实际开发和调试程序的经验

2、cpu 内存 硬盘 等等与系统性能调试相关的命令

必须熟练掌握,设置修改权限 tcp网络状态查看 各进程状春枝燃态 抓包相关等相关命令 必须熟练掌握

3、awk sed需掌握

4、共享内存的使用实现原理、然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段更大限制是多少?

5、c++

进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高道低分配,堆从低到高分配)

6、ELF是什么?

其大小与程序中全局变量的是否初始化有什么关系(注意.bss段)

7、

使用过哪些进程间通讯机制,并详细说明

8、makefile编写

,虽然比较基础,但是会被问到

9、gdb

调试相关的经验,会被问到

10、如何定位内存泄露?

11、动态链接和静态链接的区别

12、32位系统一个进程最多多少堆内存

13、多线程和多进程的区别

(重点 必须从cpu调度,上下文切换,数据共享,多核cup利用率,资源占用,等等各方面回答,然后有一个问题必须会被问到:哪些东西是一个线程私有的?答案中必须包含寄存器,否则悲催)

14、

写一个c程序辨别系统是64位 or 32位

15、

写一个c程序辨别系统是大端or小端字节序

16、

信号:列出常见的信号,信号怎么处理?

17、

i++是否原子操作?并解释为什么???????

18、

说出你所知道的各类linux系统的各类同步机制(重点),扒虚什么是死锁?如何避免死锁(每个技术面试官必问)

19、

列举说明linux系统的各类异步机制

20、

exit() _exit()的区别?

21、

如何实现守护进程?

22、

linux的内存管理机制是什么?

23、

linux的任务调度机制是什么?

24、

标准库函数和系统调用的区别?

25、

补充一个问题:系统如何将一个信号通知到进程?

二、c语言:

1、宏定义和展开(必须精通)

2、位操作(必须精通)

3、指针操作和计算(必须精通)

4、内存分配(必须精通)

5、各类库函数必须非常熟练的实现

6、哪些库函数属于高危函数,为什么?(strcpy等等)

三、c++:

1、一个String类的完整实现必须很快速写出来(注意:赋值构造,operator=是关键)

2、虚函数的作用和实现原理(必问必考,实现原理必须很熟)

3、sizeof一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响)

4、指针和引用的区别(一般都会问到)

5、多重类构造和析构的顺序

6、stl各容器的实现搭搭原理(必考)

7、extern c 是干啥的,(必须将编译器的函数名修饰的机制解答的很透彻)

8、volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻)

9、static const等等的用法,(能说出越多越好)

四、数据结构或者算法:

1、《离散数学》范围内的一切问题皆由可能被深入问到(最重要,最体现功底,最能加分,特别是各类树结构的实现和应用)

2、各类排序:大根堆的实现,快排(如何避免最糟糕的状态?),bitmap的运用等等

3、hash, 任何一个技术面试官必问(例如为什么一般hashtable的桶数会取一个素数?如何有效避免hash结果值的碰撞)

五、网络编程:

1、tcp与udp的区别(必问)

2、udp调用connect有什么作用?

3、tcp连接中时序图,状态图,必须非常非常熟练

4、socket服务端的实现,select和epoll的区别(必问)

5、epoll哪些触发模式,有啥区别?(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)

6、大规模连接上来,并发模型怎么设计

7、tcp结束连接怎么握手,time_wait状态是什么,为什么会有time_wait状态?哪一方会有time_wait状态,如何避免time_wait状态占用资源(必须回答的详细)

8、tcp头多少字节?哪些字段?(必问)

9、什么是滑动窗口(必问)

10、connect会阻塞,怎么解决?(必考必问,提示:设置非阻塞,返回之后用select检测状态)

11、如果select返回可读,结果只读到0字节,什么情况?

12、keepalive 是什么东东?如何使用?

13、列举你所知道的tcp选项,并说明其作用。

14、socket什么情况下可读?

六、db:

1、mysql,会考sql语言,服务器数据库大规模数据怎么设计,db各种性能指标

1.掌握基础旦卖操扰谈作命令

2.Linuxshell编程。

重要的是要体系的学习,模李逗可以报个班什么的。

linux 多核并发编程的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux 多核并发编程,Linux下实现高效多核并发编程技巧,linux tcp socket并发编程,调用accept函数后调用fork。两台电脑测试,为何accept返回的描述符是相同的?,如何实现linux下多线程之间的互斥与同步,Linux开发需要什么呢??需要具备什么基础呢??的信息别忘了在本站进行查找喔。


数据运维技术 » Linux下实现高效多核并发编程技巧 (linux 多核并发编程)