linux中多线程解析

时间:2024-04-12 00:03:47

Linux系统下的多线程遵循POSIX线程接口,称为 pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux 下pthread的实现是通过系统调用clone()来实现的。clone()是 Linux所特有的系统调用,它的使用方式类似fork,关于clone()的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序 pthread_create.c。

一个重要的线程创建函数原型:
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg);

返回值:若是成功建立线程返回0,否则返回错误的编号
    形式参数:
                pthread_t *restrict tidp 要创建的线程的线程id指针
                const pthread_attr_t *restrict attr 创建线程时的线程属性
                void* (start_rtn)(void) 返回值是void类型的指针函数
                void *restrict arg   start_rtn的行参
                
例程1:                               
    功能:创建一个简单的线程
    程序名称:pthread_create.c      
/********************************************************************************************
**    Name:pthread_create.c
**    Used to study the multithread programming in Linux OS
**    Author:zeickey
**    Date:2006/9/16       
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/

#include <stdio.h>
#include <pthread.h>

void *myThread1(void)
{
    int i;
    for (i=0; i<100; i++)
    {
        printf("This is the 1st pthread,created by zieckey./n");
        sleep(1);//Let this thread to sleep 1 second,and then continue to run
    }
}

void *myThread2(void)
{
    int i;
    for (i=0; i<100; i++)
    {
        printf("This is the 2st pthread,created by zieckey./n");
        sleep(1);
    }
}

int main()
{
    int i=0, ret=0;
    pthread_t id1,id2;
   
    ret = pthread_create(&id2, NULL, (void*)myThread1, NULL);
    if (ret)
    {
        printf("Create pthread error!/n");
        return 1;
    }
   
    ret = pthread_create(&id2, NULL, (void*)myThread2, NULL);
    if (ret)
    {
        printf("Create pthread error!/n");
        return 1;
    }
   
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
   
    return 0;
}

我们编译此程序:
# gcc pthread_create.c -lpthread

因为pthread的库不是linux系统的库,所以在进行编译的时候要加上-lpthread,否则编译不过,会出现下面错误
thread_test.c: 在函数 ‘create’ 中:
thread_test.c:7: 警告: 在有返回值的函数中,程序流程到达函数尾
/tmp/ccOBJmuD.o: In function `main':thread_test.c:(.text+0x4f):对‘pthread_create’未定义的引用
collect2: ld 返回 1

运行,我们得到如下结果:
# ./a.out
This is the 1st pthread,created by zieckey.
This is the 2st pthread,created by zieckey.
This is the 1st pthread,created by zieckey.
This is the 2st pthread,created by zieckey.
This is the 2st pthread,created by zieckey.
This is the 1st pthread,created by zieckey.
....

两个线程交替执行。
此例子介绍了创建线程的方法。
下面例子介绍向线程传递参数。

例程2:
    功能:向新的线程传递整形值
    程序名称:pthread_int.c
/********************************************************************************************
**    Name:pthread_int.c
**    Used to study the multithread programming in Linux OS
**    Pass a parameter to the thread.
**    Author:zeickey
**    Date:2006/9/16       
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/

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

void *create(void *arg)
{
    int *num;
    num=(int *)arg;
    printf("create parameter is %d /n",*num);
    return (void *)0;
}
int main(int argc ,char *argv[])
{
    pthread_t tidp;
    int error;
   
    int test=4;
    int *attr=&test;
   
    error=pthread_create(&tidp,NULL,create,(void *)attr);

if(error)
        {
        printf("pthread_create is created is not created ... /n");
        return -1;
        }
    sleep(1);
    printf("pthread_create is created .../n");
    return 0;       
}

编译方法:

gcc -lpthread pthread_int.c -Wall

执行结果:

create parameter is 4
pthread_create is created is  created ...

例程总结:
    可以看出来,我们在main函数中传递的整行指针,传递到我们新建的线程函数中。
    在上面的例子可以看出来我们向新的线程传入了另一个线程的int数据,线程之间还可以传递字符串或是更复杂的数据结构。

例程3:
    程序功能:向新建的线程传递字符串
        程序名称:pthread_string.c
/********************************************************************************************
**    Name:pthread_string.c
**    Used to study the multithread programming in Linux OS
**    Pass a ‘char*‘ parameter to the thread.
**    Author:zeickey
**    Date:2006/9/16       
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void *create(void *arg)
{
    char *name;
    name=(char *)arg;
    printf("The parameter passed from main function is %s  /n",name);
    return (void *)0;
}

int main(int argc, char *argv[])
{
    char *a="zieckey";
    int error;
    pthread_t tidp;

error=pthread_create(&tidp, NULL, create, (void *)a);

if(error!=0)
    {
        printf("pthread is not created./n");
        return -1;
    }
    sleep(1);
    printf("pthread is created... /n");
    return 0;
}

编译方法:

gcc -Wall pthread_string.c -lpthread

执行结果:
The parameter passed from main function is zieckey
pthread is created...

例程总结:
    可以看出来main函数中的字符串传入了新建的线程中。

例程4:
    程序功能:向新建的线程传递字符串
        程序名称:pthread_struct.c
/********************************************************************************************
**    Name:pthread_struct.c
**    Used to study the multithread programming in Linux OS
**    Pass a ‘char*‘ parameter to the thread.
**    Author:zeickey
**    Date:2006/9/16       
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

struct menber
{
    int a;
    char *s;
};

void *create(void *arg)
{
    struct menber *temp;
    temp=(struct menber *)arg;
    printf("menber->a = %d  /n",temp->a);
    printf("menber->s = %s  /n",temp->s);
    return (void *)0;
}

int main(int argc,char *argv[])
{
    pthread_t tidp;
    int error;
    struct menber *b;
    b=(struct menber *)malloc( sizeof(struct menber) );
    b->a = 4;
    b->s = "zieckey";

error = pthread_create(&tidp, NULL, create, (void *)b);

if( error )
    {
        printf("phread is not created.../n");
        return -1;
    }
    sleep(1);
    printf("pthread is created.../n");
    return 0;
}

编译方法:

gcc -Wall pthread_struct.c -lpthread

执行结果:
menber->a = 4
menber->s = zieckey
pthread is created...

例程总结:
    可以看出来main函数中的一个结构体传入了新建的线程中。
    线程包含了标识进程内执行环境必须的信息。他集成了进程中的所有信息都是对线程进行共享的,包括文本程序、程序的全局内存和堆内存、栈以及文件描述符。

例程5:
    程序目的:验证新建立的线程可以共享进程中的数据
    程序名称:pthread_share.c

/********************************************************************************************
**    Name:pthread_share_data.c
**    Used to study the multithread programming in Linux OS
**    Pass a ‘char*‘ parameter to the thread.
**    Author:zeickey
**    Date:2006/9/16       
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

static int a=4;
void *create(void *arg)
{
    printf("new pthread ... /n");
    printf("a=%d  /n",a);
    return (void *)0;
}

int main(int argc,char *argv[])
{
    pthread_t tidp;
    int error;
   
    a=5;

error=pthread_create(&tidp, NULL, create, NULL);

if(error!=0)
    {
        printf("new thread is not create ... /n");
        return -1;
    }
   
    sleep(1);
   
    printf("new thread is created ... /n");
    return 0;
}
   
  编译方法:

gcc -Wall pthread_share_data.c -lpthread

执行结果:
new pthread ...
a=5
new thread is created ...

例程总结:
可以看出来,我们在主线程更改了我们的全局变量a的值的时候,我们新建立的线程则打印出来了改变的值,可以看出可以访问线程所在进程中的数据信息。

2、线程的终止

如果进程中任何一个线程中调用exit,_Exit,或者是_exit,那么整个进程就会终止,
    与此类似,如果信号的默认的动作是终止进程,那么,把该信号发送到线程会终止进程。
    线程的正常退出的方式:
       (1) 线程只是从启动例程中返回,返回值是线程中的退出码
       (2) 线程可以被另一个进程进行终止
       (3) 线程自己调用pthread_exit函数
    两个重要的函数原型:

#include <pthread.h>
void pthread_exit(void *rval_ptr);
/*rval_ptr 线程退出返回的指针*/

int pthread_join(pthread_t thread,void **rval_ptr);
   /*成功结束进程为0,否则为错误编码*/

例程6
    程序目的:线程正常退出,接受线程退出的返回码
    程序名称:pthread_exit.c

/********************************************************************************************
**    Name:pthread_exit.c
**    Used to study the multithread programming in Linux OS
**    A example showing a thread to exit and with a return code.
**    Author:zeickey
**    Date:2006/9/16        
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/

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

void *create(void *arg)
{
    printf("new thread is created ... /n");
    return (void *)8;
}

int main(int argc,char *argv[])
{
    pthread_t tid;
    int error;
    void *temp;

error = pthread_create(&tid, NULL, create, NULL);

if( error )
    {
        printf("thread is not created ... /n");
        return -1;
    }
    error = pthread_join(tid, &temp);

if( error )
    {
        printf("thread is not exit ... /n");
        return -2;
    }
    
    printf("thread is exit code %d /n", (int )temp);
    return 0;
}

编译方法:

gcc -Wall pthread_exit.c -lpthread

执行结果:
new thread is created ...
thread is exit code 8

例程总结:
可以看出来,线程退出可以返回线程的int数值。线程退出不仅仅可以返回线程的int数值,还可以返回一个复杂的数据结构。

例程7
    程序目的:线程结束返回一个复杂的数据结构
    程序名称:pthread_return_struct.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

struct menber
{
    int a;
    char *b;
}temp={8,"zieckey"};
void *create(void *arg)
{
    printf("new thread ... /n");
    return (void *)&temp;
}

int main(int argc,char *argv[])
{
    int error;
    pthread_t tid;
    struct menber *c;

error = pthread_create(&tid, NULL, create, NULL);
   
    if( error )
    {
        printf("new thread is not created ... /n");
        return -1;
    }
    printf("main ... /n");

error = pthread_join(tid,(void *)&c);

if( error )
    {
        printf("new thread is not exit ... /n");
        return -2;
    }
    printf("c->a = %d  /n",c->a);
    printf("c->b = %s  /n",c->b);
    sleep(1);
    return 0;
}

编译方法:

gcc -Wall pthread_return_struct.c -lpthread

执行结果:

main ...
new thread ...
c->a = 8
c->b = zieckey

例程总结:
一定要记得返回的数据结构要是在这个数据要返回的结构没有释放的时候应用,
如果数据结构已经发生变化,那返回的就不会是我们所需要的,而是脏数据
3、线程标识

函数原型:
   
#include <pthread.h>
pthread_t pthread_self(void);

pid_t getpid(void);
    getpid()用来取得目前进程的进程识别码,函数说明

例程8
    程序目的:实现在新建立的线程中打印该线程的id和进程id
    程序名称:pthread_id.c
 
/********************************************************************************************
**    Name:pthread_id.c
**    Used to study the multithread programming in Linux OS.
**    Showing how to get the thread's tid and the process's pid.
**    Author:zeickey
**    Date:2006/9/16       
**    Copyright (c) 2006,All Rights Reserved!
*********************************************************************************************/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> /*getpid()*/

void *create(void *arg)
{
    printf("New thread .... /n");
    printf("This thread's id is %u  /n", (unsigned int)pthread_self());
    printf("The process pid is %d  /n",getpid());
    return (void *)0;
}

int main(int argc,char *argv[])
{
    pthread_t tid;
    int error;

printf("Main thread is starting ... /n");

error = pthread_create(&tid, NULL, create, NULL);

if(error)
    {
        printf("thread is not created ... /n");
        return -1;
    }
    printf("The main process's pid is %d  /n",getpid());
    sleep(1);
    return 0;
}

编译方法:

gcc -Wall -lpthread pthread_id.c

执行结果:

Main thread is starting ...
The main process's pid is 3307
New thread ....
This thread's id is 3086347152
The process pid is 3307

介绍linux线程的基本概念,线程间的互斥和同步机制,分析了linuxpthread库的API函数,并结合一个例子阐述多线程编程的核心技术,最后总结出多线程编程应注意的事项。

关键词 线程进程 同步 互斥
中图分类号:TP316 文献标识码:A
1.引言
目前,许多流行的多任务操作系统都提供线程机制,线程就是程序中的单个顺序控制流。利用多线程进行程序设计,就是将一个程序(进程)的任务划分为执行的多个部分(线程) ,每一个线程为一个顺序的单控制流,而所有线程都是并发执行的,这样,多线程程序就可以实现并行计算,高效利用多处理器。线程可分为用户级线程和内核级线程两种基本类型。用户级线程不需要内核支持,可以在用户程序中实现,线程调度、同步与互斥都需要用户程序自己完成。内核级线程需要内核参与,由内核完成线程调度并提供相应的系统调用,用户程序可以通过这些接口函数对线程进行一定的控制和管理。Linux操作系统提供了LinuxThreads库,它是符合POSIX1003.1c标准的内核级多线程函数库。在linuxthreads库中提供了一些多线程编程的关键函数,在多线程编程时应包括pthread.h文件。
2.LinuxThread中的关键库函数
2.1线程的创建和终止
int pthread_create(pthread_t * pthread,const pthread_attr_t *attr,void *(*start_routine(*void)),void *arg);调用此函数可以创建一个新的线程,新线程创建后执行start_routine 指定的程序。其中参数attr是用户希望创建线程的属性,当为NULL时表示以默认的属性创建线程。arg是向start_routine 传递的参数。当成功创建一个新的线程时,系统会自动为新线程分配一个线程ID号,并通过pthread 返回给调用者。
void pthread_exit(void *value_ptr);调用该函数可以退出线程,参数value_ptr是一个指向返回状态值的指针。
2.2线程控制函数
pthread_self(void);为了区分线程,在线程创建时系统为其分配一个唯一的ID号,由pthread_create()返回给调用者,也可以通过pthread_self()获取自己的线程ID。
Int pthread_join (pthread- t thread , void * *status);这个函数的作用是等待一个线程的结束。调用pthread_join()的线程将被挂起直到线程ID为参数thread 指定的线程终止。
int pthread_detach(pthread_t pthread);参数pthread代表的线程一旦终止,立即释放调该线程占有的所有资源。
2.3线程间的互斥
互斥量和临界区类似,只有拥有互斥量的线程才具有访问资源的权限,由于互斥对象只有一个,这就决定了任何情况下共享资源(代码或变量)都不会被多个线程同时访问。使用互斥不仅能够在同一应用程序的不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。Linux中通过pthread_mutex_t来定义互斥体机制完成互斥操作。具体的操作函数如下
pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);初使化一个互斥体变量mutex,参数attr表示按照attr属性创建互斥体变量mutex,如果参数attr为NULL,则以默认的方式创建。
pthread_mutex_lock(pthread_mutex_t *mutex);给一个互斥体变量上锁,如果mutex指定的互斥体已经被锁住,则调用线程将被阻塞直到拥有mutex的线程对mutex解锁为止。
Pthread_mutex_unlock(pthread_mutex_t *mutex);对参数mutex指定的互斥体变量解锁。
2.4线程间的同步
同步就是线程等待某一个事件的发生,当等待的事件发生时,被等待的线程和事件一起继续执行。如果等待的事件未到达则挂起。在linux操作系统中是通过条件变量来实现同步的。
Pthread_cond_init(pthread_cond_t *cond,const pthread_cond_t *attr);这个函数按参数attr指定的属性初使化一个条件变量cond。
Pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);等待一个事件(条件变量)的发生,发出调用的线程自动阻塞,直到相应的条件变量被置1。等待状态的线程不占用CPU时间。
pthread_cond_signal(pthread_cond_t *cond);解除一个等待参数cond指定的条件变量的线程的阻塞状态。
3.多线程编程的应用实例。
在这里利用多线程技术实现生产者和消费者问题,生产者线程向一缓冲区中写数据,消费者线程从缓冲区中读取数据,由于生产者线程和消费者线程共享同一缓冲区,为了正确读写数据,在使用缓冲队列时必须保持互斥。生产者线程和消费者线程必须满足:生产者写入缓冲区的数目不能超过缓冲区容量,消费者读取的数目不能超过生产者写入的数目。在程序中使用了一个小技巧来判断缓冲区是空还是满。在初始化时读指针和写指针为0;如果读指针等于写指针,则缓冲区是空的;如果(写指针+ 1) % N 等于读指针,则缓冲区是满的,%表示取余数,这时实际上有一个单元空出未用。下面是完整的程序段和注释。

#include<stdio.h>
#include<pthread.h>

#define BUFFER_SIZE 8

struct prodcons {
    int buffer[BUFFER_SIZE];
    pthread_mutex_t lock;      //互斥LOCK
    int readpos , writepos;
    pthread_cond_t notempty;   //缓冲区非空条件判断
    pthread_cond_t notfull;    //缓冲区未满条件判断
};

void init(struct prodcons * b){
    pthread_mutex_init(&b->lock,NULL);
    pthread_cond_init(&b->notempty,NULL);
    pthread_cond_init(&b->notfull,NULL);

b->readpos=0;
    b->writepos=0;
}

void put(struct prodcons* b,int data){
    pthread-_mutex_lock(&b->lock);

if((b->writepos + 1) % BUFFER_SIZE == b->readpos)
    {
        pthread_cond_wait(&b->notfull, &b->lock) ;
    }
   
    b->buffer[b->writepos]=data;
    b->writepos++;
   
    if(b->writepos >= BUFFER_SIZE)
        b->writepos=0;
   
    pthread_cond_signal(&b->notempty);
    pthread_mutex_unlock(&b->lock);
}
int get(struct prodcons *b){
    int data;

pthread_mutex_lock(&b->lock);
    if(b->writepos == b->readpos)
    {
        pthread_cond _wait(&b->notempty, &b->lock);
    }
   
    data = b->buffer[b->readpos];
    b->readpos++;

if(b->readpos >= BUFFER_SIZE)
        b->readpos=0;
   
    pthread_cond_signal(&b->notfull);
    pthread_mutex_unlock(&b->lock);

return data;
}

#define OVER (-1)
struct prodcons buffer;

void *producer(void *data)
{
    int n;
   
    for(n = 0; n < 10000; n++)
    {
        printf("%d \n", n) ;
        put(&buffer, n);
    }
   
    put(&buffer, OVER);
   
    return NULL;
}
void *consumer(void * data)
{
    int d;
   
    while(1)
    {
        d = get(&buffer);

if(d == OVER)
            break;
       
        printf("%d\n", d);
    }

return NULL;
}

int main(void)
{
    pthread_t th_a, th_b;
   
    void *retval;
   
    init(&buffer);
   
    pthread_create(&th_a, NULL, producer, 0);
    pthread_create(&th_b, NULL, consumer, 0);
   
    pthread_join(th_a, &retval);
    pthread_join(th_b, &retval);
   
    return 0;
}

上面的例子中,生产者负责将1到1000的整数写入缓冲区,而消费者负责从同一个缓冲区中读取写入的整数并打印出来。因为生产者和消费者是两个同时运行的线程,并且要使用同一个缓冲区进行数据交换,因此必须利用一种机制进行同步。通过上面的例子我们可以看到,多线程的最大好处是,除堆栈之外,几乎所有的数据均是共享的,因此线程间的通讯效率很高;缺点:因为共享所有数据,从而非常容易导致线程之间互相破坏数据,这一点在编程时必须注意。
4.结束语
Linux中基于POSIX标准的很好的支持了多线程技术,它减少了程序并发执行时的系统开销,提高了计算机的工作效率。在具体编程过程中要了解线程的间的关系,还要考虑共享数据的保护,在互斥和同步机制下保证代码的高效运行,程序编译时用gcc -D –REENTRANT -libpthread.xx.so filename.c编译。