这是一个面试问题。
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次?
这是对的吗?
答案 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
循环的每次迭代都是这样的:
没有人说第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 *
实际上如何生成适当的Class
或Class *
对象能够做什么。空指针可能会导致问题 - 但是,为了问题,我们假设 Thread t2 = create_thread( myX.foo() );
是有效的,并且创建了一个新线程并等待'启动'操作让它运行。
Class *
这里我们要做一些假设。我们可能会认为myX.foo()
返回的i
无法访问myX
中的成员变量t1
。因此,即使在创建t2
之前运行了线程t1
,myX
中的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
只有t1
和t2
它不可能是无限的。因为即使单个线程正在运行,它也会增加i
值。