linux线程互斥与同步(part1)—互斥锁(mutex)的原理及其实现机制

本文通过一个简单的代码示例,介绍了线程并发操作全局变量时可能出现的数据不一致问题,并探讨了如何使用互斥锁(mutex)来解决这类问题。此外,文章还深入解析了互斥锁的工作原理及其可能引发的死锁问题。

一段代码引发的问题


首先,我们来编写一段代码,它的目的是定义一个全局变量,创建两个线程对其进行5000++的操作。

1

运行结果如下:
2

当然,目前来看,这段程序并没有任何的问题。

然而,对于此程序,结合线程的特点,我们需要明确两点:

  • 局部变量 i 不是共享的,因为它在栈中

  • gCount是共享的,因为它是全局变量,属于代码段。

我们知道,只要是共享的资源,那么它就可以看作临界资源,而临界资源的访问需要同步与互斥机制才能维持正常访问。否则可能会出现数据不一致的问题。


下面,我们来讨论一种情况:

假设上边的程序中,线程1先运行。它在物理内存中读到gcount=0后被切换出去(保存上下文信息)。当线程1重新换回来时,还认为gcount是0,因为它不会再从物理内存中读取。
而此时,线程2开始运行,它在物理内存中也读到gcount=0然后将gcount加到5000,接着又被切换出去。
然后线程1再接着运行,将gcount加到1。又被切换了出去。
最后,线程2再运行时也不会再从物理内存中读取gcount的值,它从上下文信息中得知gcount的值为1。
这样问题就出现了,本来线程2已经将gcount加到了5000,现在它又从5000变成了1。如此下去,gcount的值就是不确定的。

为什么会出现这样的问题呢?

这是因为对gcount计数器的操作是非原子性的,所以导致了数据不一致的问题。

多个线程同时访问共享数据时可能会冲突。

比如上边的两个线程都要把全局变量增加1,这个操作在某平台需要三条指令完成:

  1. 从内存读变量值到寄存器
  2. 寄存器的值加1
  3. 将寄存器的值写回内存

假设两个线程在多处理器平台上同时执行这三条指令,则可能导致最后变量只加了一次而非两次。

那为什么上边的程序结果无误呢?

其实如果是以前的电脑,可能会出错。然而现在的计算机计算速度太快了,线程之间的干扰不够严重。

为了证明确实会出现这种数据不一致的问题,我们对程序进行改造,加大线程之间的干扰力度。

如何加大线程之间的干扰力度呢?有一种比较重要且容易实现的手段:触发线程间切换


知识科普

内核态:操作系统的模式,如果用户或某程序进入了内核态,那么它的权限就会不受约束,可以做任何事。操作系统向外提供系统调用接口方便进行用户态到内核态的转变。

用户态:一般用户的模式,用户或某程序在此状态下只能调用用户代码,权限受约束。当用户想调用系统接口,执行内核代码,就要从用户态变成内核态。

触发线程间切换:在线程执行函数代码中多次进行系统调用,使其不断地从用户态到内核态。这样多个线程之间就会相互干扰。

科普结束。。。


让我们回到问题中,其实上边的程序代码中有一个系统调用:printf

2

但它调用的次数不够,而且代码也不够复杂,所以对代码进行改造,在“读取全局变量gCount的值”和“把变量的新值保存回去”这两步操作之间插入一个printf调用。它会执行write系统调用进内核,为内核调度别的线程执行提供了一个很好的时机。一个循环中重复上述操作几千次,就会观察到访问冲突的现象。

加入局部变量tmp,使其代替gCount进行++操作,将数据++的过程分成两部分,增加系统调用的次数。改造后的代码如下:

3

接下来再运行程序,就会出现上述数据不一致导致的错误结果。而且多运行几次,它的结果也是不确定的。

4

5

6

好了,折腾了半天,终于找到了问题。那么如何解决这个问题呢?

解铃还须系铃人,要解决问题,归根结底就是要解决线程之间互相干扰的问题,从而保证临界资源的原子性。

互斥锁(mutex)就是这里的解铃人。


互斥锁(mutex)


对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁(Mutex,Mutual Exclusive Lock)。

获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据。这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。

Mutex⽤用pthread_mutex_t类型的变量表示。

相关函数如下表:

5

有了互斥锁的概念,下面我们来利用它解决上边的问题,在程序中用宏PTHREAD_MUTEX_INITIALIZER初始化lock互斥锁,并加入加锁解锁函数。修改后代码如下:

6

我们再来运行代码:

7

结果正确,说明互斥锁的却解决了问题。

其实,如果我们将代码中循环次数变大,即使没有系统调用触发进程间切换,也会出错。
8

上边的程序中,将循环次数加到5亿,代码运行结果也是错的。
9

接着我们加入互斥锁
11

程序运行结果再次恢复正常(此过程时间较长,因为加锁占用系统资源,程序运行时性能变低(线程在加琐时是串行运行))。
12

到这里,我们已经解决了问题。


lock和unlock实现原理


刚才我们用互斥锁解决了进程切换引发程序运行错误的问题,那么Mutex的两个基本操作lock和unlock是如何实现的呢?

假设Mutex变量的值为1表示互斥锁空闲,这时某个进程调用lock可以获得锁。而Mutex的值为0表示互斥锁已经被某个线程获得,其它线程再调用lock只能挂起等待。

基于此,我们先来看第一种lock和unlock实现的伪代码:

11

因为unlock过程一定是原子的,所以我们把视线主要集中在lock过程。

我们知道,unlock操作中唤醒等待线程的步骤可以有不同的实现,可以只唤醒一个等待线程,也可以唤醒所有等待该Mutex的线程。然后让被唤醒的这些线程去竞争获得这个Mutex,竞争失败的线程继续挂起等待。

然而仔细观察我们就可以发现问题:上述为代码中lock函数里对Mutex变量的读取、判断和修改并非原子操作。
如果两个线程同时调用lock,这时Mutex是1,而两个线程都判断mutex>0成立。然后其中一个线程置 mutex=0,而另一个线程并不知道这一情况,也置mutex=0,于是两个线程都以为⾃⼰获得了锁。 这种情况类似于刚才对全局变量gCount的操作。

因为线程在任何时候都可能被触发导致切换,而在每个线程运行时,mutex都会首先被读到CPU里。这将导致mutex的副本过多,数据依然无法保持一致性。所以这种伪代码的实现是错误的。

下面我们来看另一种实现方法:

18

首先利用汇编指令xchdb将已置0的寄存器a1与互斥量mutex值进行交换。
只对寄存器操作,所以就不会产生物理内存中的mutex副本。
这样即使线程在执行xchgb指令和条件判断时被切换出去,也是没有任何意义的,对程序结果并没有影响。
unlock中的释放锁操作同样只用一条指令实现,以保证它的原子性。 所以这才是lock和unlock的实现原理。

注:为了实现互斥锁操作,⼤大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

知识点:上述为代码中挂起等待和唤醒等待线程操作的实现机制:

每个Mutex都有一个等待队列,一个线程要在Mutex上挂起等待,首先要把自己(pcb)加入等待队列中,然后置线程状态为睡眠状态。然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一 项,把它的状态从睡眠改为就绪,加入就绪队列。那么下次调度器函数执行时就有可能切换到被唤醒的进程。


死锁(Deadlock)


正所谓“一波未平,一波又起”,世界上并没有完美的东西。

所以尽管我们能用互斥锁解决线程互斥,但同时互斥锁的引入也会导致另外的问题。这就是死锁

如果你要问我一个互斥锁会不会产生死锁,我的答案是当然会。试想一下,一个人走在路上自己都能把自己绊倒。那么互斥锁么,呵呵……

死锁产生原因

情形一:竞争资源

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

1

情形二:线程推进顺序不当

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

2

死锁产生条件

1.互斥
2.请求与保持
3.不可抢占锁资源
4.循环等待

不难想象,如果涉及到更多的线程和更多的锁。死锁的问题将会变得更加复杂和难以判断。

所以,为了避免死锁的产生,使用互斥锁时应尽量避免同时获得多个锁,且应遵循以下原则:

线程在需要多个锁时都按相同的先后顺序(常见的是按Mutex变量的地址顺序)获得锁
⽐如一个程序中用到锁1、锁2、锁3,它们所对应的Mutex变量的地址是锁1<锁2<锁3, 那么所有线程在需要同时获得2个或3个锁时都应该按锁1、锁2、锁3的顺序获得。

尽量使用pthread_mutex_trylock调用代替 pthread_mutex_lock 调用


The End


尽管我们可以遵循上面的原则,但还是不能完全避免死锁的产生。

为了解决死锁的问题,需要引入更多的线程互斥与同步机制,如条件变量等。

关于条件变量博主已在后序博文中总结完成。小伙伴们可以点击这里:

linux线程互斥与同步(part2)—互斥锁(Mutex)的“cp”:条件变量(Condition Variable)

从标题就可以看到互斥锁和条件变量有着非比寻常的关系,看过的小伙伴就会明白博主可不是强行组“cp”哦

<think>好的,用户想用C++的三个线程来计算1到n的和,需要示例代码。首先,我得回忆一下C++多线程的基本用法。记得C++11引入了<thread>库,可以方便地创建线程。用户的问题需要三个线程同时工作,各自处理一部分数据,然后将结果汇总。 首先,得考虑如何分割任务。假设n比较大,可以将1到n分成三段,每个线程处理一段的和。比如,线程1处理1到n/3,线程2处理n/3+1到2n/3,线程3处理2n/3+1到n。不过需要注意n可能无法被3整除,所以需要调整每个线程的结束位置,避免漏掉数。 然后,每个线程需要计算自己区间的和,并将结果存储起来。因为多个线程会同时修改总和,这里需要用互斥锁来保护共享变量,或者使用原子操作。不过,更高效的方法可能是让每个线程计算自己的部分和,最后再相加,这样可以避免的开销。但用户可能希望每个线程直接累加到一个总和变量中,这就需要同步机制。 看用户的引用资料,引用[3]提到使用多线程并行求和,但没有用数组,可能用的是全局变量或者传递引用。比如,每个线程计算一部分,然后累加到一个共享变量中,这时候必须用互斥锁来保护这个变量,防止数据竞争。 另外,线程的创建和连接。在C++中,创建线程后需要join,等待它们完成。主线程可能需要等待三个子线程完成后,再输出总和。 现在,具体步骤: 1. 定义全局变量total,并初始化为0。 2. 定义互斥锁mutex,用于保护total。 3. 每个线程计算指定区间的和,累加到total。 4. 主线程创建三个线程,分别处理各自的区间。 5. 主线程等待所有子线程结束,然后输出总和。 需要注意的问题包括:区间的划分是否正确,特别是当n不能被3整除时如何处理余数。例如,当n=10时,每个线程处理3、3、4个数。或者使用更均匀的分配方法,比如将余数分配给前几个线程。 另外,线程函数的参数传递可能需要使用结构体或者lambda捕获,但C++线程的参数传递需要是静态的,或者通过引用包装。或者,使用lambda表达式来捕获局部变量,但需要注意作用域和生命周期的问题。 比如,可以定义一个函数,接受开始和结束的值,以及指向总和的指针,然后加进行累加。或者,每个线程返回自己的部分和,主线程汇总,这可能更高效,因为不需要。但用户可能希望用共享变量的方式。 根据引用[3],他们的实现是每个线程计算自己的部分和,然后主线程汇总。这可能更高效,因为避免了竞争。例如,每个线程的结果存放在一个数组中,主线程最后相加。 不过用户的问题要求三个线程,所以可能需要三个不同的函数或同一个函数的不同参数。或者,使用lambda表达式传递不同的区间参数。 综合来看,可能的实现步骤: - 将n分成三个区间,每个线程处理一个区间。 - 每个线程计算自己区间的和,存放在独立的变量中。 - 主线程等待所有线程完成后,将三个部分和相加。 这种方法不需要,因为每个线程操作自己的变量。但需要确保这些变量在主线程中可访问。或者,可以通过参数传递存储结果的引用,但需要注意线程安全。 例如,使用std::promise和std::future来传递结果,或者使用全局数组来存储部分和。 或者,更简单的方式,每个线程函数接收开始、结束和指向部分和的指针,计算后将结果存入指针位置。主线程创建三个变量,每个线程处理一部分,并将结果存入对应的变量,最后相加。 这可能更简单,不需要,因为每个线程操作不同的内存位置。 比如: int部分和1,部分和2,部分和3; 线程1计算区间1,结果存入部分和1线程2计算区间2,结果存入部分和2; 线程3计算区间3,结果存入部分和3; 主线程等待,然后总和=部分和1+部分和2+部分和3; 这种方式不需要,因为每个线程独立写入自己的变量,主线程读取时已经join了,不会有竞争。 这种方法更高效,因为没有的开销,适用于多线程并行计算。 所以,示例代码的大致结构: #include <iostream> #include <thread> using namespace std; void sum_part(int start, int end, int &result) { int sum = 0; for(int i=start; i<=end; ++i) { sum +=i; } result = sum; } int main() { int n = 100; int part1, part2, part3; int step = n /3; thread t1(sum_part, 1, step, ref(part1)); thread t2(sum_part, step+1, 2*step, ref(part2)); thread t3(sum_part, 2*step+1, n, ref(part3)); t1.join(); t2.join(); t3.join(); int total = part1 + part2 + part3; cout << total << endl; return 0; } 但这里需要注意,当n无法被3整除时,第三个线程的end应该是n,例如当n=100,每个线程处理1-33,34-66,67-100?不,100/3是33余1,所以可能正确的划分是: step = (n + 2)/3; // 向上取整? 或者,计算每个线程的起始和结束: 比如,总共有三个线程,第i个线程处理从 (i-1)*chunk_size +1 到 i*chunk_size,最后一个线程处理到n。 其中,chunk_size = (n +2)/3; 这样确保每个线程大致处理1/3的数。 例如,n=10,chunk_size=4(因为10/3=3.333,向上取整为4)。那么线程1处理1-4,线程2处理5-8,线程3处理9-10。 这样分配更合理。所以代码中需要计算每个区间的start和end。 可能需要调整划分方式,确保覆盖所有数。 可能的划分方法: int chunk_size = (n + 2) /3; // 这样每个chunk_size是向上取整的。 线程1:1 到 chunk_size 线程2: chunk_size+1到 2*chunk_size 线程3: 2*chunk_size +1到n 这样可以处理余数的情况。 例如n=10,chunk_size=4: 线程1:1-4 线程2:5-8 线程3:9-10 总和是1-10的和55。 而如果用原来的step = n/3,即step=3,那么线程3处理到3*3=9,剩下的10未被处理。所以必须调整。 因此,正确的划分应该是: int chunk_size = (n +2)/3; // 或者类似的方式。 或者,让每个线程处理不同的区间: int start1 = 1; int end1 = n/3; int start2 = end1 +1; int end2 = 2*(n/3); int start3 = end2 +1; int end3 =n; 但这样可能不够准确。例如n=10,n/3=3,所以线程1处理1-3,线程2处理4-6,线程3处理7-10。这样总和是1-3=6,4-6=15,7-10=34,总和6+15+34=55,正确。 所以,正确的划分方法是: 每个线程处理大约1/3的数,但需要确保所有数都被覆盖。 所以,在代码中,正确的划分应该是: int step = n/3; start1=1, end1=step; start2=step+1, end2=2*step; start3=2*step+1, end3=n; 这样,当n=10时,step=3,线程3处理7-10,总和正确。当n=9时,每个线程处理3,3,3,正确。 所以,在代码中,应该这样计算每个区间的start和end: int step = n /3; int start1 =1; int end1=step; int start2 = step+1; int end2 = 2*step; int start3=2*step +1; int end3=n; 然后创建三个线程,每个处理自己的区间。 但需要考虑n是否足够大,比如n=2,这时候step=0,导致区间错误。但用户的问题可能假设n足够大,或者可以处理这些边界情况。 现在,编写代码: 可能需要在sum_part函数中处理start和end,当start>end时返回0。 或者,在分配区间时确保start<=end。 例如,当n=2时,step=0,线程1处理1-0(无效),所以需要调整。 可能更好的方法是计算每个线程的区间,比如: 总共有三个线程,每个处理大致n/3个数,余数分配到前面的线程。 例如,余数r =n%3; 线程1处理 (n/3)+1 如果r>0 线程2处理 (n/3)+1 如果r>1 或者,另一种分配方式: int base = n /3; int remainder =n%3; int end1 = base + (remainder>0 ?1:0); int end2 = end1 + base + (remainder>1 ?1:0); int end3 =n; 这样,每个线程处理不同的区间。例如,n=10: base=3, remainder=1. 线程1:1-4 (3+1) 线程2:5-7 (4+1到4+3=7) 线程3:8-10. 但这样总和是1-4=10,5-7=18,8-10=27,总和55,正确。 但这种方法可能比较复杂。 或者,更简单的方式是让主线程处理余数。例如,每个线程处理base个数,余数分配给前几个线程。 比如,每个线程处理base个数,其中base =n/3,余数r =n%3. 线程1处理 base +1 如果r>0 线程2处理 base +1 如果r>1 线程3处理 base 例如,n=10,base=3, r=1. 线程1处理4个数(1-4) 线程2处理4个数(5-8) 线程3处理3个数(9-11?超过n=10) 这显然不对。所以可能需要更合理的分配。 另一种方法是使用循环分配余数。比如,对于余数r=1线程1多处理一个数;r=2,线程1线程2各多处理一个。 所以,正确的分配方式: int base =n/3; int remainder =n%3; int end1 = base + (remainder>0 ?1:0); int end2 = end1 + base + (remainder>1 ?1:0); int end3 =n; 但这样需要计算每个区间的起始和结束。 这可能比较复杂,但为了正确性,必须处理。 所以,在代码中,可能需要如下步骤: int n = ...; int base = n /3; int remainder = n%3; int start1 =1; int end1 = base + (remainder>0 ?1 :0); int start2 = end1 +1; int end2 = end1 + base + (remainder>1 ?1 :0); int start3 = end2 +1; int end3 =n; 然后线程1处理start1到end1线程2处理start2到end2,线程3处理start3到end3. 这样可以确保余数被正确分配。 例如,n=10: base=3, remainder=1. end1=3+1=4. start2=5. end2=4+3=7. start3=8. end3=10. 线程1处理1-4(4个数),线程2处理5-7(3个数),线程3处理8-10(3个数)。总和正确。 另一个例子,n=11: base=3, remainder=2. end1=3+1=4. end2=4 +3 +1=8. start3=9. end3=11. 线程1处理1-4(4),线程2处理5-8(4),线程3处理9-11(3)。总和=10+26+30=66,而正确总和是11*12/2=66。正确。 这样,这样的分配方法是正确的。 所以,代码需要动态计算每个区间的start和end,考虑余数的情况。 因此,在代码中,可以这样实现: int n = 100; int base = n /3; int remainder = n%3; int start1 =1; int end1 = base + (remainder>0 ?1 :0); int start2 = end1 +1; int end2 = end1 + base + (remainder>1 ?1 :0); int start3 = end2 +1; int end3 =n; 然后创建三个线程,每个处理自己的区间。 这样,不管n是否能被3整除,都可以正确分配。 现在,将这个逻辑写入代码。 接下来,线程函数的参数传递。在C++中,线程的参数是按值传递的,除非使用std::ref。因此,如果要将部分和作为引用传递给线程函数,需要使用std::ref。 例如,sum_part函数接受开始、结束和引用参数: void sum_part(int start, int end, int &result) { int sum =0; for(int i=start; i<=end; i++) sum +=i; result = sum; } 然后,在线程创建时: thread t1(sum_part, start1, end1, ref(part1)); thread t2(sum_part, start2, end2, ref(part2)); thread t3(sum_part, start3, end3, ref(part3)); 这样,每个线程的部分和会被存入part1, part2, part3。 主线程在join所有线程后,相加得到总和。 这样,代码就能正确运行。 现在,考虑可能的边界情况,例如n=0或n=1等。假设用户输入的n是正整数,否则可能需要处理,但示例中可以假设n>=3。 现在,编写完整的代码示例: #include <iostream> #include <thread> using namespace std; void sum_part(int start, int end, int &result) { int sum = 0; for (int i = start; i <= end; ++i) { sum += i; } result = sum; } int main() { int n = 100; // 示例输入 int part1 = 0, part2 = 0, part3 = 0; int base = n / 3; int remainder = n % 3; int start1 = 1; int end1 = base + (remainder > 0 ? 1 : 0); int start2 = end1 + 1; int end2 = end1 + base + (remainder > 1 ? 1 : 0); int start3 = end2 + 1; int end3 = n; thread t1(sum_part, start1, end1, ref(part1)); thread t2(sum_part, start2, end2, ref(part2)); thread t3(sum_part, start3, end3, ref(part3)); t1.join(); t2.join(); t3.join(); int total = part1 + part2 + part3; cout << "Total sum: " << total << endl; return 0; } 这个代码应该能正确计算1到n的和,使用三个线程。例如,当n=100时,总和应为5050,代码运行结果是否正确? 验证:base=33,remainder=1. end1=33+1=34. 线程1处理1-34,和是(34*35)/2=595. 线程2处理35到34+33=67,和是(67*68/2)-(34*35/2)= (4556 - 595)= 3961? 但实际计算应该是35到67的和:(35+67)*33/2 = 102*33/2= 51*33=1683. 线程3处理68到100,和是(100+68)*33/2= 168*33/2=84*33=2772. 总和:595+1683+2772=5050,正确。 所以,代码逻辑正确。 现在,用户可能的问题: 1. 如何确保线程安全?如果使用共享变量是否需要互斥锁? 在这个示例中,每个线程写入自己的变量,没有竞争,因此不需要。因为每个线程写入独立的内存位置,主线程在join之后读取,所以是安全的。 2. 如何处理更大的n或动态线程数? 可以扩展为根据线程数动态分配区间,但用户的问题固定为三个线程。 3. 异常处理? 例如,如果输入的n太小,可能导致区间start大于end。例如,n=2,base=0,余数=2. start1=1, end1=0+1=1. start2=2, end2=1 +0 + (余数>1?1:0). 余数是2>1,所以end2=1+0+1=2. start3=3, end3=2. 这会出错,因为start3=3大于end3=2。 所以在sum_part函数中,需要处理这种情况,即当start>end时,sum=0. 所以,修改sum_part函数: void sum_part(int start, int end, int &result) { int sum = 0; if (start <= end) { for (int i = start; i <= end; ++i) { sum += i; } } result = sum; } 这样,当start>end时,sum为0,避免错误。 例如,n=2: base=0, remainder=2. end1=0+1=1. start2=2. end2=1 +0 +1=2. start3=3. end3=2. 线程3处理3到2,sum=0. 总和是1-1=1,2-2=2,总和3,正确(1+2=3)。 因此,代码现在处理n较小的案例也是正确的。 综上,代码示例应该是正确的,处理了各种情况,包括余数分配和小的n值。</think>以下是使用C++通过三个线程计算1到n之和的示例代码: ```cpp #include <iostream> #include <thread> using namespace std; // 线程函数:计算从start到end的累加和,结果存入result void sum_part(int start, int end, int &result) { int sum = 0; if (start <= end) { for (int i = start; i <= end; ++i) { sum += i; } } result = sum; } int main() { int n = 100; // 待计算的数值范围 int part1 = 0, part2 = 0, part3 = 0; // 划分三个线程的计算区间 int base = n / 3; int remainder = n % 3; int start1 = 1; int end1 = base + (remainder > 0 ? 1 : 0); int start2 = end1 + 1; int end2 = end1 + base + (remainder > 1 ? 1 : 0); int start3 = end2 + 1; int end3 = n; // 创建并启动三个线程 thread t1(sum_part, start1, end1, ref(part1)); thread t2(sum_part, start2, end2, ref(part2)); thread t3(sum_part, start3, end3, ref(part3)); // 等待所有线程完成 t1.join(); t2.join(); t3.join(); // 汇总结果 int total = part1 + part2 + part3; cout << "Total sum: " << total << endl; return 0; } ``` ### 实现说明 1. **任务划分**:将1到n的区间分为三部分,考虑余数的分配: - 第一个线程处理前$\lfloor n/3 \rfloor + 1$(当有余数时) - 第二个线程处理中间$\lfloor n/3 \r
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值