阿里2014校招笔试题(南大)——利用thread和sleep生成字符串的伪随机序列


引言:题目具体描述记不大清了,大概是:Linux平台,利用线程调度的随机性和sleep的不准确性,生成一个各位均不相同的字符数组的伪随机序列。不得使用任何库函数。(这句记得清楚,当时在想线程库算不算可怜,题目的意思应该是:不得使用库提供的随机函数)

1.算法

当时读完题很开心,因为这题的算法曾在《编程珠玑》里看到过(取样问题ch12,p119)。算法比较简单,就是除了第一字符(下标0)以外,为其余N-1个字符各创建一个线程,每个线程先sleep一秒(也可以更长),再将对应位置的字符和第一个字符交换;N-1个线程完成后,主线程结束原理暗藏在题目中,sleep一秒后,因为sleep的不准确性,这N-1个线程几乎同时醒来(就绪)(试想如若sleep非常精确,各个线程醒来的顺序就会和创建顺序相同);又由于线程调度的随机性,这时会被执行的线程是随机的,(不知先后顺序地)执行N-1次之前所述的交换所得的基本上就是一个随机序列。不过当时想不起来pthread_create几个参数的顺序了(前面题的计算量不小,头都搞晕了),就随便按个顺序写了。回来后,按照当时的思路很快在电脑上写了出来:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
            
            
            
            
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#ifdef WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif
char str [] = "ABCDEFGH" ;
#define LEN sizeof(str) / sizeof(str[0]) - 1
pthread_t tids [ LEN ];
void * thread_func ( void * idx )
{
char tmp ;
sleep ( 1 );
tmp = str [( int ) idx ];
str [( int ) idx ] = str [ 0 ]; // str[0] is the critical resource !!
str [ 0 ] = tmp ;
return ( void * ) 0 ;
}
int main ( void )
{
int i ;
puts ( str );
for ( i = 1 ; i < LEN ; i ++ ) {
pthread_create ( & tids [ i ], NULL , thread_func , ( void * ) i );
}
sleep ( 2 );
puts ( str );
return 0 ;
}
 来自CODE的代码片
err_rand_str.c
注意:main里用sleep(2)(比N-1个线程多sleep一秒)等待其他线程只是为了写起来简单,并不严谨!

2.互斥

不过一在电脑上写出来立即意识到一个问题——第一个元素是临界资源(所有“其他线程”都想抢着用这块地);如果不做互斥访问可能会出现——有的字符出现两次(获更多)有的字符没了,这种错误不是每次都会出现:
阿里2014校招笔试题(南大)——利用thread和sleep生成字符串的伪随机序列_第1张图片

意识到这个错误之后,比较容易修改,只需将线程函数内对第一个元素的操作放入临界区中即可:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
           
           
           
           
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#ifdef WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif
char str [] = "ABCDEFGH" ;
#define LEN sizeof(str) / sizeof(str[0]) - 1
pthread_t tids [ LEN ];
pthread_mutex_t mutex ;
void * thread_func ( void * idx )
{
char tmp ;
sleep ( 1 );
tmp = str [( int ) idx ];
pthread_mutex_lock ( & mutex );
str [( int ) idx ] = str [ 0 ]; // str[0] is the critical resource !!
str [ 0 ] = tmp ;
pthread_mutex_unlock ( & mutex );
return ( void * ) 0 ;
}
int main ( void )
{
int i ;
puts ( str );
pthread_mutex_init ( & mutex , NULL );
for ( i = 1 ; i < LEN ; i ++ ) {
pthread_create ( & tids [ i ], NULL , thread_func , ( void * ) i );
}
sleep ( 2 );
puts ( str );
pthread_mutex_destroy ( & mutex );
return EXIT_SUCCESS ;
}
 来自CODE的代码片
err2_rand_str.c

这次不会再有错误。for命令连续测试10次:

阿里2014校招笔试题(南大)——利用thread和sleep生成字符串的伪随机序列_第2张图片

到此,说明这个算法没有问题。

3.同步

还应该main里的sleeep(2)改掉。main创建好N-1个线程后就应该被挂起,直到其他所有线程都“完工”后才应该被唤醒;肯定要用条件变量,main创建好其他线程后wait阻塞,其他所有线程都“完工”再被signal唤醒。为了保证这样的顺序,必须要让最后一个完工的线程知道自己是最后一个,也就是在最后一个其他线程“收工”的时候signal。只需要加个计数变量count用来标记还有多少线程没有完工,将其初始化为要创建的线程数N-1,每有一个线程“完工”就count--,判断count的值即可知道当前线程是倒数第几个完工的。(啰嗦一堆,还是上代码直观):

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
            
            
            
            
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#ifdef WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * x)
#endif
char str [] = "ABCDEFGH" ;
#define LEN sizeof(str) / sizeof(str[0]) - 1
int count ;
pthread_t tids [ LEN ];
pthread_mutex_t mutex ;
pthread_mutex_t mc ;
pthread_cond_t cond ;
void * thread_func ( void * idx )
{
char tmp ; // in TLS(Thread local space.)
sleep ( 1 );
tmp = str [( int ) idx ];
pthread_mutex_lock ( & mutex );
str [( int ) idx ] = str [ 0 ]; // str[0] is the critical resource.
str [ 0 ] = tmp ;
-- count ;
printf ( "%d %d \n " , idx , count );
if ( count == 0 ) {
puts ( "SIGNAL" );
pthread_cond_signal ( & cond );
}
pthread_mutex_unlock ( & mutex );
return ( void * ) 0 ;
}
int main ( void )
{
int i ;
puts ( str );
count = LEN - 1 ;
pthread_mutex_init ( & mutex , 0 );
pthread_mutex_init ( & mc , 0 );
pthread_cond_init ( & cond , 0 );
for ( i = 1 ; i < LEN ; i ++ ) {
pthread_create ( & tids [ i ], 0 , thread_func , ( void * ) i );
}
pthread_mutex_lock ( & mc );
puts ( "WAIT" );
pthread_cond_wait ( & cond , & mc );
pthread_mutex_unlock ( & mc );
puts ( str );
pthread_cond_destroy ( & cond );
pthread_mutex_destroy ( & mc );
pthread_mutex_destroy ( & mutex );
return EXIT_SUCCESS ;
}
 来自CODE的代码片
rand_str.c
需要稍加留意的是:count也是临界资源,需要放到临界区里(和str[0]一样);另外pthread_cond_wait接口所需的 pthread_mutex_t *  不能用维护“其他线程”临界的那个mutex,应该再定义一个mutex。因为pthread_cond_wait会原子性地阻塞当前线程同时unlock传入的mutex,pthread_cond_wait返回时,传入的mutex再次被锁。(APUEcn2e p309)

阿里2014校招笔试题(南大)——利用thread和sleep生成字符串的伪随机序列_第3张图片

注:本机环境gcc 4.6.1(MinGW)  




你可能感兴趣的:(C++,阿里巴巴,校园招聘,笔试面试)