posix多线程程序设计 pdf,posix多线程编程
接下来,我们将编写一个程序来检测两个线程是否并发执行。因为我们不知道有效完成这项任务所需的线程同步知识,所以这不是一个高效完成线程间池操作的程序。同样,我们应该利用这个事实,在一个进程中的不同线程之间共享除局部函数变量之外的所有变量。
实验-两个线程的同步执行
在这一部分中,我们创建了程序thread2.c,它是对Thread1.c的简单修改。我们添加了一个额外的文件域变量来测试哪个线程正在运行:
int run _ now=1;
当主函数执行时,我们将其设置为1,当新线程执行时,我们将其设置为2。
在main函数中,创建新线程后,我们添加以下代码:
int print _ count 1=0;
while(print_count1 20) {
if (run_now==1) {
printf( 1 );
run _ now=2;
}
否则{
睡眠(1);
}
}
如果run_now为1,我们输出1并将其设置为2。不然我们就短暂的睡一觉,再检查一遍。我们反复检查,直到值为1。这被称为忙等待,尽管我们在每次检测之间会休眠一秒钟来降低速度。我们将在本章的后面看到完成这项任务的更好的方法。
在thread_function中,这是我们新线程执行的地址。我们也会做同样的操作,只是价值相反。
int print _ count 2=0;
while(print_count2 20) {
if (run_now==2) {
printf( 2 );
run _ now=1;
}
否则{
睡眠(1);
}
}
我们删除了传递的参数和返回值,因为我们对它们不再感兴趣。
当我们运行这个程序时,我们将看到下面的输出。(我们可能会发现程序需要几秒钟才能产生输出)
$ cc-D _可重入线程3.c -o线程2-LP线程
$ ./线程2
12121212121212121212
正在等待线程完成.
螺纹连接
每个线程通过设置run_now变量通知其他线程运行,然后等待其他线程更改该变量的值。这演示了一个线程的执行是自动在两个线程之间传递的,并再次显示了两个线程共享run_now变量。
同时发生
在前一部分中,我们看到两个线程同时执行,但是我们在它们之间切换的方法笨拙而低效。幸运的是,有一个专门设计的函数集可以为我们提供更好的方法来控制线程的执行和访问临界区代码。
我们将学习两种基本方法:信号量,它就像一段代码周围的看门人;互斥,类似于保护代码段的独占设置。这两种方法类似。事实上,其中一个可以通过使用另一个方法来实现。然而,在某些情况下,问题的语义会建议使用其中之一。例如,要控制对某个共享内存的访问,一次只有一个线程可以访问,最自然的解决方案是使用互斥。但是,将对一组相同对象的访问作为一个整体来控制,例如将五条可用电话线中的一条分配给一个线程,更适合于信号量计数的方法。选择哪种方法取决于我们的偏好和最适合我们程序的机制。
使用信号量同步
信号量有两个接口函数:一个来自POSIX实时扩展并用于线程,另一个称为System V信号量,通常用于进程同步。(我们将在本章后面讨论第二个信号量。)这两个信号量是不能交互的,虽然很像,但是用的是不同的函数调用。
在这一部分中,我们来看看最简单的信号量类型,其值只有0或1的二进制信号量。还有一种更通用的信号量,使用的是有更多值的计数信号量。通常,信号量用于保护一段代码,以便在任何时候只有一个执行线程可以运行它。这样的任务需要一个二元信号量。有时候,我们希望允许一定数量的线程执行一段特定的代码;在这种情况下,我们可以使用计数信号量。因为计数信号量不常用,所以我们在这里不做深入讨论,但是我们需要指出,计数信号量只是二进制信号量的逻辑扩展,实际需要的函数调用是一样的。
信号量不像大多数特定于线程的函数那样以pthread_开头,而是以sem_开头。线程中使用了四种基本的信号量函数。都很简单。
信号量是用sem_init函数创建的,其声明如下:
#包含信号量. h
int sem_init(sem_t *sem,int pshared,unsigned int value);
这个函数初始化一个由sem指向的信号量对象,设置它的共享选项,并给它分配一个初始整数值。pshared参数控制信号量类型。如果pshared的值为0,那么这个信号量对于当前进程来说是本地的。否则,这个信号量可以在进程间共享。这里我们只对不能在进程间共享的信号量感兴趣。在写这本书的时候,Linux还不支持这种共享,当给pshared传递一个非零值时,调用就会失败。
下面的一对函数控制信号量的值,声明如下:
#包含信号量. h
int SEM _ wait(SEM _ t * SEM);
int SEM _ post(SEM _ t * SEM);
这两个函数都将指向sem_init调用初始化的信号量对象的指针作为参数。
Sem_post函数会自动将信号量的值增加1。这里的Automatic是指如果两个线程同时尝试将一个信号量的值增加1,它们不会相互影响。例如,当两个程序同时读取一个值,增加这个值,并将其写入一个文件时,就会发生这种情况。信号量总是正确地将其值增加2,因为有两个线程试图修改它。
Sem_wait函数会自动将信号值减1,但该函数总是等待,直到信号计数不为零。所以,如果我们对一个值为2的信号量调用sem_wait函数,线程将继续执行,但是信号量的值将减少到1。如果对值为0的信号量调用sem_wait函数,该函数将一直等待,直到另一个函数增加该值,这样信号量的值就不再是0。如果在sem_wait中有两个线程同时等待同一个信号量变为非零,并且这个信号量的值被第三个进程增加,那么这两个等待线程中只有一个可以减少这个信号量并继续执行,而另一个会继续等待。
函数中原子的“测试和设置”能力使得信号量如此有价值。还有一个信号量函数sem_trywait,是sem_wait函数的非阻塞模式。这里不做深入讨论,可以在手册中了解更多。
最后一个信号量函数是sem_destroy。当我们完成时,这个函数将清除信号量。其声明如下:
#包含信号量. h
int SEM _ destroy(SEM _ t * SEM);
同样,这个函数将指向信号量的指针作为参数,并清理它拥有的所有资源。如果我们试图销毁一个线程正在等待的信号量,我们将得到一个错误。
与大多数Linux函数类似,这些函数将在成功时返回0。
实验线程信号量
下面的代码thread3.c也是基于thread1.c,因为做了大量的修改,所以我们这里有一个完整的展示。
#包含stdio.h
#包括unistd.h
#包含stdlib.h
#包含字符串. h
#include pthread.h
#包含信号量. h
void * thread _ function(void * arg);
sem _ t bin _ sem
#定义工作大小1024
char WORK _ area[WORK _ SIZE];
int main()
{
int res
pthread _ t a _ thread
void * thread _ result
res=sem_init( bin_sem,0,0);
if(res!=0)
{
perror(“信号量初始化失败”);
退出(EXIT _ FAILURE);
}
res=pthread_create( a_thread,NULL,thread_function,NULL);
if(res!=0)
{
perror(“线程创建失败”);
退出(EXIT _ FAILURE);
}
输入一些文本。输入‘end’结束/n’);
while(strncmp(end ,work_area,3)!=0)
{
fgets(work_area,WORK_SIZE,stdin);
SEM _ post(bin _ SEM);
}
printf(/n等待线程完成./n’);
res=pthread_join(a_thread,thread _ result);
if(res!=0)
{
perror(“线程加入失败”);
退出(EXIT _ FAILURE);
}
printf(螺纹连接/n );
SEM _ destroy(bin _ SEM);
退出(EXIT _ SUCCESS);
}
void *thread_function
{
SEM _ wait(bin _ SEM);
while(strncmp(end ,work_area,3)!=0)
{
printf(您输入了%d个字符/n ,strlen(work _ area)-1);
SEM _ wait(bin _ SEM);
}
pthread_exit(空);
}
第一个重要的修改是包含了semaphore.h,这样我们就可以访问信号量函数。然而,在创建新线程之前,我们声明了一个信号量和一些变量,并初始化了信号量。
sem _ t bin _ sem
#定义工作大小1024
char WORK _ area[WORK _ SIZE];
int main() {
int res
pthread _ t a _ thread
void * thread _ result
res=sem_init( bin_sem,0,0);
if (res!=0) {
perror("信号量初始化失败");
退出(EXIT _ FAILURE);
}
注意,这里我们的信号量的值被初始化为0。
在main函数中,我们启动一个新线程后,通过键盘读取一些文本,存储在我们的工作区中,然后使用sem_post函数来增加信号量。
printf("输入一些文本。输入 end 结束/n ");
while(strncmp("end ",work_area,3)!=0) {
fgets(work_area,WORK_SIZE,stdin);
SEM _ post(bin _ SEM);
}
在新线程中,我们等待信号量,然后计算输入的字符数。
SEM _ wait(bin _ SEM);
while(strncmp("end ",work_area,3)!=0) {
printf("你输入了%d个字符/n ",strlen(work _ area)-1);
SEM _ wait(bin _ SEM);
}
当信号量被设置时,我们等待键盘输入。当我们有一些输入时,我们释放这个信号量,并允许第二个线程在第一个线程再次读取它之前计算字符数。
同样,两个线程共享同一个work_area数组。我们忽略一些错误检测,比如sem_wait的返回值,这样代码更简单。然而,在产品代码中,我们应该总是检测返回的错误代码,除非我们有充分的理由忽略这些检测。
让我们运行我们的程序:
$ cc-D _ REENTRANT-I/usr/include/nptl thread 3 . co thread 3-L/usr/lib/nptl-
线程库
$./线程3
输入一些文本。输入“结束”结束
黄蜂工厂
你输入16个字符
伊恩班克斯
你输入10个字符
目标
正在等待线程完成.
螺纹连接
当我们初始化信号量时,我们将它的值设置为0。因此,当线程函数启动时,sem_wait调用将阻塞并等待信号量变为非零。
在主线程中,我们等待,直到我们有了一些文本,然后使用sem_post函数增加信号量,这将立即导致另一个线程被sem_wait返回并开始执行。一旦他完成了字符数的计算,他将再次调用sem_wait并阻塞,直到主线程再次调用sem_post来增加这个信号量。
人们很容易忽略那些导致小错误的设计错误。让我们简单地修改一下这个程序thread4a.c,来说明键盘输入的文本有时会被可用的文本自动替换。我们将主函数修改成这样:
printf("输入一些文本。输入 end 结束/n ");
while(strncmp("end ",work_area,3)!=0) {
if (strncmp(work_area," FAST ",4)==0) {
SEM _ post(bin _ SEM);
strcpy(work_area," Wheeee . ");
}否则{
fgets(work_area,WORK_SIZE,stdin);
}
SEM _ post(bin _ SEM);
}
现在如果我们输入FAST,程序将调用sem_post来允许字符计数器运行,但是立即用一些不同的内容更新work_area。
$ cc-D _可重入线程4a.c -o线程4a-LP线程
$ ./thread4a
输入一些文本。输入“结束”结束
过度
你输入9个字符
快的
你输入7个字符
你输入7个字符
你输入7个字符
目标
正在等待线程完成.
螺纹连接
问题是,我们的程序依赖于文本输入,在主线程准备给它发送更多的单词进行计算之前,另一个线程有时间完成单词计数。当我们试图给它分配两个不同的词集来快速连续地计数(用键盘输入FAST然后自动替换为Wheee.),没有时间让第二个线程执行。但是,信号量已经增加了很多次,所以计数器线程将继续计算字数,并减少信号量的值,直到它再次变为零。
这个例子说明我们需要在多线程程序中仔细考虑时间。通过使用另一个信号量让主线程等待,直到计数线程有机会完成计数,可以解决这个问题。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。