设为首页 收藏本站
查看: 1020|回复: 0

[经验分享] Linux多线程编程

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-3-5 08:50:56 | 显示全部楼层 |阅读模式
“编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。”

我们就从这样一题出发,认识多线程,了解其同步机制,最后正确解答这一类题目。本文框架如下:

进程与线程
多线程的优越性
线程基本函数
多线程同步
题目代码


一.进程与线程

      进程的定义:进程是为了描述程序在并发执行时对系统资源的共享,所需的一个描述程序执行时动态特征的概念。进程是具有独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配、调度和保护的独立单位。

      线程的定义:线程也成为轻量级进程,是进程中的一个运行实体,作为CPU的调度单位。一个进程由多个线程组成,线程与同属一个进程的其他的线程共享进程所拥有的全部资源。

      同一进程内的所有线程除了共享全局变量外还共享:

进程指令;
大多数数据;
打开的文件(即描述符);
信号处理函数和信号处置;
当前工作目录;
用户ID和组ID。
     每个线程拥有各自的:

线程ID;
寄存器集合,包括程序计数器和栈指针;
栈(用于存放局部变量和返回地址);
errno;
信号掩码;
优先级。
      结构上的不同可以让我们更加了解进程和线程的相异之处:

      (1)进程是资源分配的基本单位;线程与资源分配无关,它属于某一个进程,并与进程内的其他线程共享进程的资源。

      (2)当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

      (3)线程只由先关堆栈(系统栈或用户栈)寄存器和线程控制块组成。寄存器用来存储线程内的局部变量,但不能存储其他线程的相关变量。

      (4)进程切换时涉及有关资源指针的保存和地址空间的变化;线程切换时,由于处于同一进程内,所以不涉及资源信息的保存和地址空间的变化,从而减少了操作系统的时间开销。



二.多线程的优越性

      在传统的UNIX模型中,当一个进程需要另一个实体来完成某事,它就fork一个子进程并让子进程去处理。但是fork的调用有如下缺点:

      (1)fork的代价是昂贵的。fork要把父进程的内存印象复制到子进程,并在子进程中复制所有描述符等。

      (2)fork返回之后父子进程之间信息的传递需要进程通信机制。调用fork之前父进程向尚未存在的子进程传递信息相当容易,因为子进程将从父进程数据空间及所有描述符的一个副本开始运行,但是从子进程向父进程返回信息却比较费力。

      针对这两点,多线程技术相应而生,它具有如下优越性:

      (1)它是一种非常"节俭"的多任务操作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

      (2)线程间方便的通信更加方便。由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。但是,同一进程内的所有线程共享相同的全局内存,这样线程之间的通信就变得相当简单,随之而来的就是同步问题。



三.基本线程函数

1.pthread_create函数,创建线程

int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void *), void *arg);
      pthread_t *tid:一个进程内的各个线程是由线程ID标识的,如果新线程创建成功,返回tid指针。

      const pthread_attr_t *attr:每个线程有多个属性,包括优先级、初始栈大小、是否是一个守护线程等等。

      void *(*func)(void *):线程启动函数,线程从调用这个函数开始,或显示结束(调用pthread_exit()),或隐式结束(让该函数返回)。

      void *arg:线程执行func函数的传递参数。

2.pthread_join函数,等待一个线程终止

int pthread_join(pthread_t *tid, void **status);
      void **status:二级指针,如果status指针非空,那么所等待线程的返回值将存放在status指向的位置。

3.pthread_self函数,返回线程ID

int pthread_self(void);
      跟进程比较,相当于getpid。

4.pthread_detach函数,线程分离

int pthread_detach(pthread_t tid);  
      线程或者是可汇合的(joinable),或者是脱离的(detach)。当可汇合的线程终止时,线程ID和退出状态将保留,知道另外一个线程调用pthread_join。脱离的线程终止时,释放所有的资源,因此我们不能等待它终止。若要一个线程知道另一个线程的终止时间,我们就要保留第二个线程的可汇合性。

5.pthread_exit函数,线程终止

int pthread_exit(void **status);  
      若线程未脱离,那么它的线程ID和退出状态将保留到另外一个线程调用pthread_join为止。



四.多线程的同步

      有了上面的基本函数还不足以完成本题的要求,为什么呢?因为题目要求按照ABCABC...的方式打印,而3个线程却在抢占资源,所以无法控制排列顺序。这时就需要用到多线程编程中的同步技术。

      对于多线程编程来说,同步就是同一时间只允许一个线程访问资源,而其他线程不能访问。多线程有3种同步方式:

互斥锁
条件变量
读写锁
1.互斥锁

      互斥锁是最基本的同步方式,它用来保护一个“临界区”,保证任何时刻只由一个线程在执行其中的代码。这个“临界区”通常是线程的共享数据。

      下面三个函数给一个互斥锁上锁和解锁:

int pthread_mutex_lock(pthread_mutex_t *mptr);

int pthread_mutex_trylock(pthread_mutex_t *mptr);

int pthread_mutex_unlock(pthread_mutex_t *mptr);
  假设线程2要给已经被线程1锁住的互斥锁(mutex)上锁(即执行pthread_mutex_lock(mutex)),那么它将一直阻塞直到到线程1解锁为止(即释放mutex)。

      如果互斥锁变量时静态分配的,通常初始化为常值PTHREAD_MUTEX_INITIALIZER,如果互斥锁是动态分配的,那么在运行时调用pthread_mutex_init函数来初始化。

2.条件变量

      互斥锁用于上锁,而条件变量则用于等待,通常它都会跟互斥锁一起使用。

int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
  通常pthread_cond_signal只唤醒等待在相应条件变量上的一个线程,若有多个线程需要被唤醒呢,这就要使用下面的函数了:

int pthread_cond_broadcast(pthread_cond_t *cptr);
3.读写锁

      互斥锁将试图进入连你姐去的其他简称阻塞住,而读写锁是将读和写作了区分,读写锁的分配规则如下:

      (1)只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读;

      (2)仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写。

int pthread_rwlock_rdlock(pthread_relock_t *rwptr);
int pthread_rwlock_wrlock(pthread_relock_t *rwptr);
int pthread_rwlock_unlock(pthread_relock_t *rwptr);


五.题目代码

      分析此题:

1.主线程main创建3个线程tid0,tid1,tid2;

2.设一个全局变量num,互斥锁mutex保护此临界区保证每次只有一个线程访问num;

3.若抢占到资源的线程tid并不是我们需要的,那么让它阻塞;

4.若抢占到资源的线程tid正好是我们需要的,那么就打印相应字母;

5.解锁,唤醒其他两个等待线程;

6.main函数等待3个线程打印结束才结束。

      代码如下:



#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<unistd.h>
#include<pthread.h>

int num=0;

static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

void *func(void *);

int main()
{
    pthread_t tid[3];
    int ret=0,i;
    for(i=0;i<3;i++)
        if((ret=pthread_create(&tid[i],NULL,func,(void*)i))!=0)
            printf("create thread_%c error\n",i+'A');
    for(i=0;i<3;i++)
        pthread_join(tid[i],NULL);
    printf("\n");
    return 0;
}

void *func(void *argc)
{
    int i;
    for(i=0;i<10;i++)
    {
        pthread_mutex_lock(&mutex);
        while(num!=(int)argc)
            pthread_cond_wait(&cond,&mutex);
        printf("%c",num+'A');
        num=(num+1)%3;
        pthread_mutex_unlock(&mutex);
        pthread_cond_broadcast(&cond);
    }
    pthread_exit(0);
}





运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-15432-1-1.html 上篇帖子: 在 Red Hat Enterprise Linux Server安装桌面 下篇帖子: 问题:在开机启动时,提示“unexpected inconsistency;RUN fsck MAN... 多线程 Linux
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表