linux多线程学习(1)

首先,编写一个耗时的单线程程序:

#include<cstdio>
#include<unistd.h>
int main()
{
    sleep(5);
    printf("program exited.\n");
}

编译并运行这段程序,该程序5秒后输出,sleep期间不再响应其它消息或执行其他操作。为了更好地处理这种耗时的操作,我们需要使用多线程编程。

先从书上抄些东西:

  进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。

  线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main函数,将程序的启动点提供给操作系统。主执行线程终止了,进程也就随之终止。

  每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。

  多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前部分的计算机是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。

拥有下述特性的程序可以使用线程:

  • 工作可以被多个任务同时执行,或者数据可以同时被多个任务操作。
  • 阻塞与潜在的长时间I/O等待。
  • 在某些地方使用很多CPU循环而其他地方没有。
  • 对异步事件必须响应。
  • 一些工作比其他的重要(优先级中断)。

多线程也可以用于串行程序,模拟并行执行。很好例子就是经典的web浏览器,运行在单CPU的电脑上,许多东西可以同时“显示”出来。

使用线程编程的几种常见模型:

  • 管理者/工作者(Manager/worker):一个单线程,作为管理器将工作分配给其它线程(工作者),典型的,管理器处理所有输入和分配工作给其它任务。至少两种形式的manager/worker模型比较常用:静态worker池和动态worker池。
  • 管道(Pipeline):任务可以被划分为一系列子操作,每一个被串行处理,且是被不同的线程并发处理。汽车装配线可以很好的描述这个模型。比如IDM等下载软件的文件分块同时下载。
  • Peer:和manager/worker模型相似,但是主线程在创建了其它线程后,自己也参与工作。

接下来看看实现多线程编程的接口pthread。

线程管理

首先是创建和结束线程

函数

int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*),void *arg);
void pthread_exit(void* retval);
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

创建线程:

  最初,main函数包含了一个缺省的线程。其它线程则需要程序员显式地创建。pthread_create创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用。

pthread_create的参数与返回值:

    第一个参数为指向线程标识符的指针。不能设置为NULL。

    第二个参数用来设置线程属性。可设置NULL为缺省值。

    第三个参数是线程运行函数的起始地址,即线程将会执行一次的C函数。

    最后一个参数是传递给运行函数的参数。传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。

         若成功则返回0,否则返回出错编号。

  一个进程可以创建的线程最大数量取决于系统实现。

  一旦创建,线程就称为peers,可以创建其它线程。线程之间没有指定的结构和依赖关系。

  有这样一对问答:

    Q:一个线程被创建后,怎么知道操作系统何时调度该线程使之运行?

    A:除非使用了线程的调度机制,否则线程何时何地被执行取决于操作系统的实现。强壮的程序应该不依赖于线程执行的顺序。

  也就是说,多线程程序的运行结果可能是不确定的,因为不知道系统会何时运行该线程。

线程属性:

  线程具有属性,用pthread_attr_t表示,在对该结构进行处理之前必须进行初始化,在使用后需要对其去除初始化。我们用pthread_attr_init函数对其初始化,用pthread_attr_destroy对其去除初始化。还有其它的一些函数用于查询和设置线程属性结构的指定属性。

  线程属性结构如下:

typedef struct
{
    int                   etachstate;      //线程的分离状态
    int                   schedpolicy;     //线程调度策略
    structsched_param     schedparam;      //线程的调度参数
    int                   inheritsched;    //线程的继承性
    int                   scope;           //线程的作用域
    size_t                guardsize;       //线程栈末尾的警戒缓冲区大小
    int                   stackaddr_set;   //线程的栈设置
    void*                 stackaddr;       //线程栈的位置
    size_t                stacksize;       //线程栈的大小
}pthread_attr_t;

  调用pthread_attr_init之后,pthread_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。

    如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果经pthread_attr_destroy去除初始化之后的pthread_attr_t结构被pthread_create函数调用,将会导致其返回错误。

  线程属性的其他特性和用法之后再讨论。

结束终止:

  结束线程的方法有一下几种:

  • 线程从主线程(main函数的初始线程)返回。
  • 线程调用了pthread_exit函数。
  • 其它线程使用 pthread_cancel函数结束线程。
  • 调用exec或者exit函数,整个进程结束。

  pthread_exit用于显式退出线程。典型地,pthread_exit()函数在线程完成工作时或不再需要时候被调用,退出线程。如果main函数在调用了pthread_exit()后将退出,尽管在main中已经没有可执行的代码了,进程和所有线程将保持存活状态,其他线程将会继续执行。否则,它们会随着main的结束而终止。对于正常退出,可以免于调用pthread_exit(),除非你想得到一个返回值。程序员可以可选择地指定终止状态,当任何线程连接(join)该线程时,该状态就返回给连接(join)该线程的线程。pthread_exit()函数并不会关闭文件,任何在线程中打开的文件将会一直处于打开状态,直到线程结束。

 

现在我们使用多线程来使我们的程序能在sleep期间执行其他操作。

#include<cstdio>
#include<pthread.h>
#include<unistd.h>

void* printhello(void*)
{
    for(int i=1;i<=4;i++)
    {
        sleep(1);
        printf("hello! %d sec has past\n",i);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,printhello,NULL);
    sleep(5);
    printf("program exited.\n");
}

该程序利用多线程,在main函数sleep的时候还能进行输出。

如果我们在main中使用pthread_exit()退出

#include<cstdio>
#include<pthread.h>
#include<unistd.h>

void* printhello(void*)
{
    for(int i=1;i<=4;i++)
    {
        sleep(1);
        printf("hello! %d sec has past\n",i);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,printhello,NULL);
    pthread_exit(NULL);  //退出main的线程
    printf("program exited.\n");  //这段代码将不被执行
}

会发现子线程仍然能继续执行,如果将

    pthread_exit(NULL);  //退出main的线程

改为

    return 0;

程序将会直接终止,不输出。

向线程传递参数

  pthread_create()函数允许程序员向线程的start_rtn函数传递一个参数。当多个参数需要被传递时,可以通过定义一个结构体包含所有要传的参数,然后用pthread_create()传递一个指向改结构体的指针,来打破传递参数的个数的限制。 所有参数都应该传引用传递并转化成(void*)。

  要安全地向一个新创建的线程传递数据应确保所传递的数据是线程安全的(不能被其他线程修改)。

  下面的代码片段演示了如何向一个线程传递一个简单的整数。

#include<cstdio>
#include<pthread.h>
#include<unistd.h>

void* printid(void* id)
{
    printf("my thread id is %d\n",*(int*)id);

}
int main()
{
    pthread_t tid;
    pthread_create(&tid,NULL,printid,(void*)tid);
    pthread_exit(NULL);//不使用return是因为主函数退出太快以至于子线程没输出就被终止了
}

  下面的代码片段演示了如何向一个线程传递结构体参数。

#include<cstdio>
#include<pthread.h>
#include<unistd.h>

struct point
{
    int x,y;
};
void* print(void* p)
{
    printf("I got a point (%d,%d).\n",((point*)p)->x,((point*)p)->y);

}
int main()
{
    pthread_t tid[10];
    point p[10];
    for(int i=0;i<10;i++)
    {
        p[i].x=p[i].y=i;
        pthread_create(&tid[i],NULL,print,(void*)&p[i]);
    }
    pthread_exit(NULL);
}

  而下面的代码是错误的,循环会在线程访问传递的参数前改变传递给线程的地址,这将造成段错误(Segmentation fault)。

#include<cstdio>
#include<pthread.h>
#include<unistd.h>

void* printid(void* i)
{
    printf("i got a number %d\n",*(int*)i);

}
int main()
{
    pthread_t tid[10];
    for(int i=0;i<10;i++)
    {
        pthread_create(&tid[i],NULL,printid,(void*)i);
    }
    pthread_exit(NULL);
}
时间: 2024-10-27 11:22:21

linux多线程学习(1)的相关文章

Linux多线程学习总结

线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体:进程相当于运行中程序的一种抽象.根据运行环境的调度者的身份,线程可分为内核线程和用户线程.内核线程,在有的系统上称为LWP(Light Weight Process,轻量级线程),运行在内核空间,由内核调度:用户线程运行在用户空间,由线程库来调度.当进程的一个内核线程获得CPU的使用权时,它就加载并运行一个用户线程.可见,内核线程相当于用户线程运行的'容器',一个进程可以拥有M个内核线程和N个用户线程,其中M<=N,并且一个系统的所

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

《Linux多线程服务端编程——使用muduo C++网络库》学习笔记

第一章 线程安全的对象生命期管理 第二章 线程同步精要 第三章 多线程服务器的适用场合与常用编程模型 第四章 C++多线程系统编程精要 1.(P84)11个常用的最基本Pthreads函数: 2个:线程的创建和等待结束(join).封装为muduo::Thread 4个:mutex的创建.销毁.加锁.解锁.封装为muduo::MutexLock 5个:条件变量的创建.销毁.等待.通知.广播.muduo::Condition 2.(P85)不推荐使用读写锁的原因是它往往造成提高性能的错觉(允许多个

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

Linux程序设计学习笔记----多线程编程基础概念与基本操作

转载请注明出处,http://blog.csdn.net/suool/article/details/38542543,谢谢. 基本概念 线程和进程的对比 用户空间资源对比 每个进程在创建的时候都申请了新的内存空间以存储代码段\数据段\BSS段\堆\栈空间,并且这些的空间的初始化值是父进程空间的,父子进程在创建后不能互访资源. 而每个新创建的线程则仅仅申请了自己的栈,空间,与同进程的其他线程共享该进程的其他数据空间包括代码段\数据段\BSS段\堆以及打开的库,mmap映射的文件与共享的空间,使得

Linux 程序设计学习笔记----POSIX 文件及目录管理

转载请注明:http://blog.csdn.net/suool/article/details/38141047 问题引入 文件流和文件描述符的区别 上节讲到ANSI C 库函数的实现在用户态,流的相应资源也在用户空间,但无论如何实现最终都需要通过内核实现对文件的读写控制.因此fopen函数必然调用了对OS的系统调用.这一调用在LINUX下即为open, close, read, write等函数.这些都遵循POSIX标准. so,在linux系统中是如何通过POSIX标准实现对文件的操作和目

Qt多线程学习:创建多线程

[为什么要用多线程?] 传统的图形用户界面应用程序都仅仅有一个运行线程,而且一次仅仅运行一个操作.假设用户从用户界面中调用一个比較耗时的操作,当该操作正在运行时,用户界面一般会冻结而不再响应.这个问题能够用事件处理和多线程来解决. [Linux有线程的概念吗?] 传统的UNIX系统也支持线程的概念,但一个进程里仅仅同意有一个线程,这样多线程就是多进程.Linux下的Posix线程(pthreads)是一种轻量级的进程的移植性实现,线程的调度由内核完毕,每一个线程都有自己的编号.假设使用线程,整体

说一说本人对linux系统学习的方法和经验

摘要: 相信大伙都听说过linux系统,然而对于这个系统,总使让新手感觉茫然,诺达的系统.下面是一段百度中的介绍: 相信大伙都听说过linux系统,然而对于这个系统,总使让新手感觉茫然,诺达的系统.下面是一段百度中的介绍: Linux系统是一套开源的并且能够自由传播的类似与Unix操作系统,是一个基于POSIX和UNIX的多任务.多用户.支持多线程和多CPU的操作系统.它能运行主要的UNIX工具软件.应用程序和网络协议.Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操

Linux系统学习笔记:序

Linux系统学习笔记:序 ??Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统.它能运行主要的UNIX工具软件.应用程序和网络协议.它支持32位和64位硬件.Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统. 本人使用的Linux为Ubuntu,主要以<APUE>(第3版)为学习蓝本. 1. Unix/Linux 体系结构 如图: 内核的接口被称为系统调用.公用函数库构建在