循环在多线程C ++程序中执行多少次?

时间:2012-02-04 16:27:07

标签: c++ linux multithreading pthreads

这是一个面试问题。

class X 
{
    int i = 0 ;

public: 
    Class *foo()
    {
        for (  ;  i < 1000 ; ++i )
        {
            // some code but do not change value of i 
        }
    }
}    

int main()
{
    X myX ; 
    Thread  t1 = create_thread( myX.foo() ) ;
    Thread  t2 = create_thread( myX.foo() ) ; 
    Start t1 ...
    Start t2 ...
    join(t1)
    joint(t2)
}

Q1:如果代码在1-cpu处理器上运行,那么在最坏的情况下for循环运行多少次?

Q2:如果代码在2-cpu处理器上运行,那么在最坏的情况下for循环运行多少次呢?

我的想法:

循环可能无限次运行,因为在另一个线程更新i的值之前,线程可以多次运行它。

或者,当t1暂停时,t2运行1000次然后我们有1000 x 1000次?

这是对的吗?

4 个答案:

答案 0 :(得分:4)

create_thread( myX.foo() )调用create_thread,返回值为myX.foo()myX.foo()在主线程上运行,因此myX.i最终的值为1000(这是两次调用myX.foo()后的值)。

如果代码实际上意味着同时在两个不同的线程上运行myX.foo()两次,那么代码将具有未定义的行为(由于访问myX.i中的竞争条件)。所以,是的,循环可以运行无限次(或零次,或者程序可以决定起床吃百吉饼)。

答案 1 :(得分:2)

无论在哪种类型的系统上运行此代码。线程之间的切换方式相同。 所有案件的最坏情况是: 1000 + 1000 * 999 * 998 * 997 * ... * 2 * 1次。 (不正确!!!正确的是更新)

当第一个线程试图增加一个变量(它已经读取了一个值但尚未写入)时,第二个线程可以从i的起始值开始所有循环,但是当第二个线程完成它的最后一个循环时,第一个线程增加值为i,第二个线程再次启动它的长期工作:)

* 更新(更多细节)

对不起,真正的公式是:

1000 + 1000 + 999 + 998 + ... + 2 + 1次, 或500999

循环的每次迭代都是这样的:

  1. 检查条件。
  2. 做一份工作。
  3. 从i
  4. 读取值
  5. 提高阅读价值
  6. 将值写入i
  7. 没有人说第2步有恒定的时间。所以我认为它有一个变化,适合我最糟糕的情况。

    这是最糟糕的情况:

    迭代1:

    [1st thread]进行第一次循环迭代(非常长的工作时间)的步骤1-4

    [2nd thread]完成所有循环(1000次),但上次不检查条件

    迭代2:

    [1] Maskes第5步,所以现在i == 1,并进行下一循环迭代的步骤1-4

    [2]从当前i(999次)

    进行所有循环

    迭代3:与之前相同,但是i == 2

    ...

    迭代1000:与之前相同,但是i == 999

    最后,我们将有1000次迭代,每次迭代将从第一个线程执行1个循环代码,从第二个线程执行(1000 - 迭代次数)。

答案 2 :(得分:2)

如果准确转录代码,这是一个糟糕的面试问题。

class X 
{
    int i = 0;

此表示法无效C ++。 G ++说:

3:13: error: ISO C++ forbids initialization of member ‘i’ [-fpermissive]
3:13: error: making ‘i’ static [-fpermissive]
3:13: error: ISO C++ forbids in-class initialization of non-const static member ‘i’ 

我们将忽略这一点,假设代码写得更像:

class X
{
    int i;
public:
    X() : i(0) { }

原始代码继续:

public: 
    Class *foo()
    {
        for (  ;  i < 1000 ; ++i )
        {
            // some code but do not change value of i 
        }
        return 0;  // Added to remove undefined behaviour
    }
}

不清楚Class *是什么 - 示例中未指定类型Class

int main()
{
    X myX; 
    Thread  t1 = create_thread( myX.foo() );

由于此处调用foo()并且其返回值传递给create_thread(),因此循环将在此处执行1000次 - 重要的不是它是否是多核系统。循环完成后,返回值将传递给create_thread()

由于我们没有create_thread()的规范,因此无法预测它将对Class *返回的myX.foo()做什么,不仅仅是myX.foo()可以说明Class *实际上如何生成适当的ClassClass *对象能够做什么。空指针可能会导致问题 - 但是,为了问题,我们假设 Thread t2 = create_thread( myX.foo() ); 是有效的,并且创建了一个新线程并等待'启动'操作让它运行。

Class *

这里我们要做一些假设。我们可能会认为myX.foo()返回的i无法访问myX中的成员变量t1。因此,即使在创建t2之前运行了线程t1myX中的myX.foo()也不会受到t2的干扰,并且当主线程执行此语句时,循环将再执行0次。 i的结果将用于创建线程myX,它不会再干扰 Start t1 ... Start t2 ... 中的Class *。我们将在下面讨论这些假设的变化。

myX.foo()

允许线程运行;他们执行从myX返回的Class *隐含的任何内容。但线程既不能引用也不能(因此)修改 join(t1) joint(t2) ;除非} 以某种方式提供访问权限,否则他们无权访问它。

t1

线程完成......

t2

因此,循环体在创建Class *之前执行1000次,并在创建i之前再执行0次。无论是单核还是多核机器都无关紧要。

实际上,即使您假设t1授予线程访问create_thread() 的权限,您也可以假设i立即开始运行(可能在t1之前{1}}返回主线程),只要它不修改create_thread(),行为就保证为'1000和0次'。

显然,如果i在调用myX时开始运行并修改create_thread()中的Thread t1 = create_thread(myX.foo); Thread t2 = create_thread(myX.foo); ,那么行为就是不确定的。但是,虽然线程处于暂停动画状态,直到'开始'操作,但没有不确定性,'1000和0次'仍然是正确的答案。


替代方案

如果create_thread()来电被误记,代码是:

t2

其中指向成员函数的指针传递给i,那么答案就完全不同了。现在,在线程启动之前不会执行该函数,并且答案是不确定机器上是否有一个CPU或多个CPU。它归结为线程调度问题,还取决于代码的优化方式。几乎1000和2000之间的任何答案都是合理的。

在足够奇怪的情况下,答案甚至可能更大。例如,假设执行t1并将t1读为0,然后暂停以让i运行; t2处理迭代0..900,然后回写i,并将控制转移到t1,将i的内部副本递增为1并将其写回,然后被暂停,t2再次运行并读取t1并再次从1到900运行,然后让t2再次运行......等等。在这种令人难以置信的情况下(Class *和{{1}}执行的代码可能是相同的 - 虽然这一切都取决于{{1}}实际上是什么),但可能会有很多迭代

答案 3 :(得分:1)

最差情况假设main只有t1t2

,有2000次

它不可能是无限的。因为即使单个线程正在运行,它也会增加i值。