b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

POSIX线程的详细说明

电脑杂谈  发布时间:2020-07-19 03:34:20  来源:网络整理

posix线程接口_posix 进程 线程_posix进程和线程区别

支持内存共享的简单工具

丹尼尔·罗宾斯

2000年7月1日发布

对于每个优秀的程序员来说,知道如何正确使用线程是一项基本素质. 线程类似于进程. 像进程一样,线程在时间片中由内核管理. 在单处理器系统中,内核使用时间切片以与进程相同的方式模拟线程的并发执行. 在多处理器系统中,与多个进程一样,线程实际上可以并发执行.

那么对于大多数协作任务,为什么多线程优于多个独立进程?这是因为线程共享相同的内存空间. 不同的线程可以访问内存中的相同变量. 因此,程序中的所有线程都可以读取或写入声明的全局变量. 如果您曾经使用过fork()编写重要的代码,您将意识到该工具的重要性. 为什么?尽管fork()允许创建多个进程,但它也带来以下通信问题: 如何使多个进程彼此通信,其中每个进程都有自己独立的内存空间. 这个问题没有简单的答案. 尽管本地IPC(进程间通信)种类很多,但它们都遇到两个重要的障碍:

双重坏事: 成本和复杂性是坏事. 如果您曾经为支持IPC而奋斗过,那么您将不胜感激线程提供的简单共享内存机制. 由于所有线程都驻留在相同的内存空间中,因此POSIX线程无需进行昂贵且复杂的长途调用. 只要使用简单的同步机制,程序中的所有线程都可以读取和修改现有数据结构. 无需通过文件描述符将数据转储或压缩到紧密的共享内存空间中. 仅此一个原因就足以让您考虑应该采用单进程/多线程模式而不是多进程/单线程模式.

不仅如此. 线程化也非常快. 与标准fork()相比,线程导致的开销非常小. 内核不需要单独复制进程的内存空间或文件描述符. 这节省了大量CPU时间,使线程创建比新进程创建快十到一百倍. 因此,可以大量使用线程,而不必过多担心CPU或内存不足. 由fork()引起的大量CPU使用率不再存在. 这意味着只要在程序中有意义,通常就可以创建线程.

posix进程和线程区别_posix 进程 线程_posix线程接口

当然,与进程一样,线程将利用多个CPU. 如果该软件是为多处理器系统设计的,那么这确实是一个很大的功能(如果该软件是开源的,则最终可能会在许多平台上运行). 某些类型的线程程序(尤其是CPU密集型程序)的性能将随着系统中处理器数量的增加而线性增加. 如果您正在编写占用大量CPU的程序,则肯定要尝试在代码中使用多个线程. 一旦掌握了线程编码,就可以以新颖的方式解决编码问题,而无需使用笨拙的IPC和其他复杂的通信机制. 所有这些功能一起使多线程编程变得更加有趣,快速和灵活.

如果您熟悉Linux编程,则可能知道__clone()系统调用. __clone()与fork()类似,但也具有许多线程特征. 例如,使用__clone(),新的子进程可以有选择地共享父进程的执行环境(内存空间,文件描述符等). 这是好的一面. 但是__clone()也有缺点. __clone()联机帮助指出:

“ __ clone调用特定于Linux平台,不适合实现可移植程序. 要编写线程应用程序(相同内存空间的多线程控制),最好使用实现POSIX 1003.1的库c线程API,例如Linux-Threads库. 请参见pthread_create(3thr). ”

尽管__clone()具有线程的许多特征,但它不是可移植的. 当然,这并不意味着它不能在代码中使用. 但是,在考虑在软件中使用__clone()时应权衡这一事实. 幸运的是,正如__clone()联机帮助指出的那样,还有一个更好的选择: POSIX线程. 如果要编写可移植的多线程代码,这些代码可以在Solaris,FreeBSD,Linux和其他平台上运行,则POSIX线程是很自然的选择.

以下是用于POSIX线程的简单示例程序:

#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
 void *thread_function(void *arg) {
  int i;
  for ( i=0; i<20; i++) {
    printf("Thread says hi!\n");
    sleep(1);
  }
  return NULL;
}
int main(void) {
  pthread_t mythread;
  
  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    printf("error creating thread.");
    abort();
  }
  if ( pthread_join ( mythread, NULL ) ) {
    printf("error joining thread.");
    abort();
  }
  exit(0);
}

要编译此程序,只需将程序另存为thread1.c,然后输入:

$ gcc thread1.c -o thread1 -lpthread

posix线程接口_posix进程和线程区别_posix 进程 线程

要运行,请输入:

$ ./thread1

thread1.c是一个非常简单的线程程序. 尽管它没有实现任何有用的功能,但可以帮助您了解线程的操作机制. 下面,我们逐步了解该程序的作用. 变量mythread在main()中声明,类型为pthread_t. pthread_t类型在pthread.h中定义,通常称为“线程ID”(缩写为“ tid”). 您可以将其视为线程句柄.

在声明mythread之后(请记住mythread只是一个“ tid”或要创建的线程的句柄),请调用pthread_create函数来创建一个实际的活动线程. 不要被“ if”语句中的pthread_create()所迷惑. 由于pthread_create()成功执行时返回零,而失败时返回非零值,因此将pthread_create()函数调用放在if()语句中只是为了便于检测失败的调用. 让我们看一下pthread_create参数. 第一个参数&mythread是指向mythread的指针. 第二个参数当前为NULL,可用于定义线程的某些属性. 由于默认线程属性是适用的posix 进程 线程,因此只需将此参数设置为NULL.

第三个参数是新线程启动时要调用的函数的名称. 在此示例中,该函数名为thread_function(). 当thread_function()返回时,新线程将终止. 在此示例中,线程函数未实现大型函数. 它仅输出“线程说声你好!” 20次,然后退出. 请注意,thread_function()接受void *作为参数,并且返回值类型也为void *. 这意味着void *可用于将任何类型的数据传递给新线程,并且在新线程完成时可以返回任何类型的数据. 如何将任意参数传递给线程?非常简单只需在pthread_create()中使用第四个参数即可. 在此示例中,由于不需要将任何数据传递给平凡的thread_function(),因此第四个参数设置为NULL.

您可能已经推测,在pthread_create()成功返回之后,该程序将包含两个线程. 等一下,两个线程?我们不是只创建一个线程吗?是的,我们只创建了一个过程. 但是主程序也是一个线程. 可以这样理解: 如果您编写的程序根本不使用POSIX线程,则该程序为单线程(此单线程称为“主”线程). 创建新线程后,该程序总共将有两个线程.

我认为您目前至少有两个重要问题. 第一个问题是创建新线程后主线程如何运行. 答案是,主线程继续按顺序执行程序的下一行(在此示例中,执行“ if(pthread_join(...))”). 第二个问题是新线程结束时该怎么办. 答案是,新线程首先停止,然后作为清理过程的一部分,它等待与另一个线程合并或“连接”.

现在,看看pthread_join(). 就像pthread_create()将一个线程分成两个线程一样,pthread_join()将两个线程合并成一个线程. pthread_join()的第一个参数是tidmythread. 第二个参数是指向空指针的指针. 如果void指针不为NULL,则pthread_join将线程的void *返回值放在指定位置. 由于我们不在乎thread_function()的返回值,因此将其设置为NULL.

posix进程和线程区别_posix 进程 线程_posix线程接口

您会注意到thread_function()花费了20秒来完成. 在thread_function()结束之前很久,主线程已经调用了pthread_join(). 如果发生这种情况,主线程将中断(进入睡眠状态)并等待thread_function()完​​成. 当thread_function()完​​成时,pthread_join()将返回. 此时,该程序只有一个主线程. 程序退出时,所有新线程都已使用pthread_join()合并. 这是处理程序中创建的每个新线程的方法. 如果没有合并新线程,则仍然不利于系统的最大线程限制. 这意味着,如果未正确清理线程,最终将导致pthread_create()调用失败.

如果您使用过fork()系统调用,则您可能熟悉父进程和子进程的概念. 当使用fork()创建另一个新进程时,新进程是子进程,而原始进程是父进程. 这将创建非常有用的层次关系,尤其是在等待子进程终止时. 例如,waitpid()函数使当前进程等待所有子进程终止. waitpid()用于在父进程中实现一个简单的清理过程.

POSIX线程更有趣. 您可能已经注意到,我一直在故意避免使用术语“父线程”和“子线程”. 这是因为POSIX线程中没有这种层次关系. 尽管主线程可以创建一个新线程,而新线程可以创建另一个新线程,但是POSIX线程标准将它们视为等效级别. 因此,等待子线程退出的概念在这里毫无意义. POSIX线程标准不记录任何“家庭”信息. 缺乏家庭信息的主要含义是: 如果要等待线程终止,则必须将线程的tid传递给pthread_join(). 线程库无法为您确定tid.

对于大多数开发人员而言,这不是一个好消息,因为它会使具有多个线程的程序复杂化. 但是不用担心. POSIX线程标准提供了有效管理多个线程所需的所有工具. 实际上,不存在父子关系这一事实为在程序中使用线程开辟了更多创造性的方式. 例如,如果有一个名为线程1的线程,并且线程1创建了一个称为线程2的线程,则线程1不需要调用pthread_join()来合并线程2,程序中的任何其他线程都可以做到这一点. 当编写大量使用线程的代码时,这可能会使有趣的事情发生. 例如,您可以创建一个包含所有已停止线程的全局“死线程列表”,然后让特殊的清理线程等待已停止线程添加到列表中. 此清理线程调用pthread_join()合并刚刚停止的线程. 现在,只需一个线程即可巧妙,高效地处理所有清理工作.

现在,让我们看一些代码,它执行一些意外的操作. thread2.c的代码如下:

#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int myglobal;
 void *thread_function(void *arg) {
  int i,j;
  for ( i=0; i<20; i++) {
    j=myglobal;
    j=j+1;
    printf(".");
    fflush(stdout);
    sleep(1);
    myglobal=j;
  }
  return NULL;
}
int main(void) {
  pthread_t mythread;
  int i;
  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    printf("error creating thread.");
    abort();
  }
  for ( i=0; i<20; i++) {
    myglobal=myglobal+1;
    printf("o");
    fflush(stdout);
    sleep(1);
  }
  if ( pthread_join ( mythread, NULL ) ) {
    printf("error joining thread.");
    abort();
  }
  printf("\nmyglobal equals %d\n",myglobal);
  exit(0);
}

与第一个程序一样,该程序创建一个新线程. 主线程和新线程都将全局变量myglobal递增20倍. 但是程序本身产生了一些意外的结果. 请输入编译后的代码:

$ gcc thread2.c -o thread2 -lpthread

posix线程接口_posix进程和线程区别_posix 进程 线程

要运行,请输入:

$ ./thread2

输出:

$ ./thread2
..o.o.o.o.oo.o.o.o.o.o.o.o.o.o..o.o.o.o.o
myglobal equals 21

非常意外!因为myglobal从零开始,所以主线程和新线程均向其加一20次,并且myglobal的值在程序结束时应等于40. 由于myglobal的输出为21,所以一定有问题. 那是什么?

放弃?好吧,让我解释一下发生了什么. 首先看一下函数thread_function(). 注意如何将myglobal复制到局部变量“ j”?然后将j增加1,睡眠一秒钟,然后将新的j值复制到myglobal?这是关键. 想象一下,如果主线程在新线程将myglobal的值复制到j之后立即将myglobal递增一,那会发生什么?当thread_function()将j的值写回到myglobal时,它将覆盖主线程所做的更改.

在编写线程程序时,应避免这种无用的副作用,否则只会浪费时间(当然,除了编写有关POSIX线程的文章外). 那么posix 进程 线程,如何解决这个问题呢?

由于将myglobal复制到j并等待一秒钟再写回时存在问题,因此可以尝试避免使用临时局部变量,而将myglobal直接增加一个. 尽管此解决方案适用于此特定示例,但仍然不正确. 如果我们在myglobal上执行相对复杂的数学运算而不是简单地添加一个,则此方法将失败. 但是为什么呢?

要了解此问题,您必须记住线程是同时运行的. 即使在单处理器系统上运行(内核使用时间分片来模拟多任务)也是可能的. 从程序员的角度来看,想象两个线程同时执行. thread2.c的问题是因为thread_function()依赖于以下参数: 在myglobal递增一之前,myglobal不会被修改大约一秒钟. 当对myglobal进行更改时,一个线程需要某种方式通知其他线程“保留”. 我将在下一篇文章中解释如何执行此操作. 到时候见.


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-285463-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      • 喜田亚由美
        喜田亚由美

        于是俺们厂里买了好多跟废品差不多的浙商设备

      热点图片
      拼命载入中...