3 POSIX 多任务及同步机制-实验3: POSIX多线程及同步机制
一.实验目的
·掌握POSIX多线程的同步机制
·通过实验深入理解操作系统的进程概念、线程概念,Linux的进程概念和线程概念
·通过实验深入理解操作系统中的进程和线程的并发问题和同步问题
二.实验背景
·POSIX同步机制
·互斥锁、自旋锁、条件变量、读写锁等·互斥锁在使用时往往是成对出现的,首先是加锁,然后是解锁。加锁函数和解锁函数分别为:
pthread_mutex_lock (pthread_mutex_t *mutex);
pthread_mutex_unlock (pthread_mutex_t *mutex);
三.关键代码及分析
线程退出代码:
在pthread_create 函数中传入了第4个参数&i,也就是线程编号的地址。4个子线程使用相同的例程函数 my_thread 来创建:
·int thread_id[THREADNO]; //线程参数数组
·POSIX 同步机制
·POSIX 互斥锁
互斥锁是一个 pthread_mutex_t 类型的变量,在使用时需要对其进行声明:
pthread_mutex_t mutex;
互斥锁在使用之前需要对其初始化,在使用完毕后需要对其进行撤销。互斥锁的撤销函数为:
int pthread_mutex_destory(pthread_mutex_t *mutex);
参数mutex 指向要销毁的互斥锁的指针。
互斥锁的初始化函数:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
该函数以动态方式创建互斥锁,第二个参数attr指定了新建互斥锁的属性。如果参数attr为NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁。互斥锁的属性在创建锁的时候指定。pthread_ mutexattr_init 函数调用成功会返回零.其他任何返回值都表示出现了错误,互斥锁被初始化为未锁住状态。互斥锁在使用时往往是成对出现的,首先是加锁,然后是解锁。加锁函数和解锁函数分别为:
从实现原理上来讲,pthread_mutex_t,定义的mutex属于sleep-waiting类型的锁,也就阻塞类型的锁。Pthread_mutxe_lock()操作如果没有加锁成功的话就会调用system_wait()的系统调用,并将调用它的线程加上到这个mutex的等待队列中。
·互斥锁使用示例
首先在主函数中声明和初始化互斥锁,示例如下:
pthread_mutex_t mut; //锁的声明
pthread_mutex_init(&mut, NULL); //初始化pthread_mutex_lock(&mut); // 加锁
//临界区
pthread_mutex_unlock(&mut); // 解锁在加锁和解锁两行语句之间的代码就称为临界区,在一个时间点上只允许一个线程进入.
四.实验结果与分析
gcc -o mythread_posix2 mythread_posix2.c -lpthread
./mythread_posix2 1000000
多次执行输出的结果不同
在mythread函数中传人的参数,也就是线程编号对应的地址,其中存储的内容被转为整型数值赋给变量thread_ arg. 然后线程利用该参数输出信息表明自己的身份。如果从ANSIC的视角出发,把my_ thread 看成是一个单纯的调用函数.通过传送地址指针的方法应该是可以得到正确输出的。但问题是my_thread 不是一个单纯的调用函数,当pthread_create调用pthread_create时,成功就会创建一个新的线程。 新的线程创建后,不会马上就得到处理机运行,而必须等到调度结点后由调度函数调度才能获得运行。因此,在创建子线程的for循环被迅速执行到停止循环时,即i为4时,此时&i指向的内容为4:但在for循环迅速完成的同时,获得&i参数的新创建子线程尚未来得及执行;当for循环结束后的某个时刻,各个子线程得到了运行机会执行参数获取语句thread arg = * (int *) args;,但此时&i其实指向的已经是4了所以4个子线程最终都指向同一个地址,内容为4.该问题从一个侧面反映了多任务与单任务程序的差异以及调度对任务执行的影响。
·正确的线程参数传递方法
线程传递参数错误是由于线程并发过程中执行上下文环境的改变造成的。解决该问题的一个方法是在for结构中pthread_create 语句后加人seep(1)这样的休眠语句,也就是每当创建一个子线程时就让主线程休眠一段时间,以便新创建的子线程获得执行时间。换句话说,就是让主线程休眼从面保证子线程执行时使用的地址参数仍然是该线程创建时的参数这样虽然可以得到正确的运行结果但这种方法严重削弱了程序的并发性,降低了系统效率。
从线程执行上下文环境的角度来看,解决上述问题的一个方法是:保持传人线程的参数在主线程中具有不变的地址。一个简单的策略是在主线程中定义一个全局线程编号数组int thread_id[THREADNO]。在创建线程时,传递的是该数组的地址,具体如下:
temp = pthread_create(&thread[i], NULL, my_thread, (void*)(thread_id+i))
这样就可以保证创建的子线程身份识别正确了。
gcc -o mythread_posix2m mythread_posix2m.c -lpthread
./mythread_posix2m 1000000
程序执行结果显示counter的计数值仍然是情误的。其原因在于my._thread 函数中用于记录计数次数的共享变量counter会被多个线程同时使用;每个线程在使用counter时b并未意识到其他线程也在修改coumter 的数值。因此造成了相互之间的覆道,也就是资源竞争。
· gcc -o mythread_posix3 mythread_posix3.c -lpthread
· ./mythread_posix3 100000000
由于对counter变量加上互斥锁,就不会存在多个线程同时使用共享变量counter了,counter的数字正确了。