跟着iMX28x开发套件学linux-10

十、linux应用编程之八:线程

线程是包含在进程内部的顺序执行流,是进程中的实际运作单位,也是操作系统能够进行调度的最小单位。一个进程中可以并发多条线程,每条线程并行执行不同的任务。

简单来说,进程是由线程组成的,线程是系统调度的最小单位,进程是拥有资源的基本单位而线程共享进程的资源。

线程的内容有点复杂,分线程创建与终止,连接与分离,线程属性,互斥量,条件变量五部分做笔记。

 1.线程创建与终止

1) 函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

 输入参数:thread,线程ID,实际上是个结构体。attr,属性对象,暂时先置为NULLstart_routine, 函数指针,指向线程执行函数。arg,线程执行函数的输入参数。

 返 值:成功返回0,失败返回一个非零错误码。

 示    例:rc = pthread_create(&threadID, NULL, PrintHello, (void *)t);

 

2) 函数原型:void pthread_exit(void *retval);

 输入参数:retval,线程终止时向主线程返回的参数。

 返 值:无

 示    例:pthread_exit(0);

 

3) 伪代码:void* 执行函数{... pthread_exit(NULL);}

main{

  pthread_create(&线程ID, NULL, 执行函数, (void*)参数);

  pthread_exit(NULL); //一定不要忘了这里要退出主线程

}

4) 代码示例:

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

#define NUM_THREAD    5

void* PrintHello(void* threadid){
    long id;
    id = (long)threadid;
    printf("Thread#%ld : Hello World! It's me, thread #%ld\n", id, id);
    pthread_exit(NULL);
}

int main(){
    pthread_t threads[NUM_THREAD];
    int rc;
    long i;
    for(i=0; i<NUM_THREAD; i++){
        printf("main : creating thread %ld\n", i);
        rc = pthread_create(&threads[i], NULL, PrintHello, (void*)i);
        if(rc){
            fprintf(stderr,"create thread #%ld error\n",i);
            exit(-1);
        }
    }
    printf("main : main exit!\n");
    pthread_exit(NULL);
    return 0;
}

运行结果:

 

 2.连接与分离

线程分为分离线程和非分离线程,分离线程在退出时会立即释放系统资源并返回,而非分离线程在退出时不会立即释放资源,需要另一个线程为它调用pthread_join()函数。这样的意义是:当线程A退出时,为其调用join函数的线程B可以获得线程A的返回值。但是这样会导致一个问题,如果没有一个线程A为线程B调用join函数,那线程B就一直无法退出,也就无法释放资源,只能等到进程终止时,线程B才能退出。对于长时间运作的程序来说,完成工作的线程长期不退出会导致占用资源过多的现象。因此,对于长时间运行的程序,最好将线程设置为分离线程或者为线程调用join函数,当然,设置为分离线程的线程将无法返回值。

1) 函数原型:int pthread_detach(pthread_t thread);

 输入参数:thread,线程ID,由变量定义以及create函数得到。

 返 值:int,成功返回0,失败返回非零错误码。

 示    例:pthread_detach(pthread_self());

 

2) 函数原型:int pthread_join(pthread_t thread, void **retval);

 输入参数:thread,线程IDretval,返回值存放地址。

 返 值:int,成功返回0,失败返回非零错误码。

 示    例:void *statusrc = pthread_join(threadID, &status);

 说    明:viod **retval作为要被终止线程返回值的存放地址,这里用了&(void*)status,但是实 际上要检查或者引用这个返回值时,可以直接用(long)status来引用,保留疑问,待解 决。因为执行函数的返回值类    型为void*,也就是说void*类型才是有效数据,又因为 C语言中没有引用类型,因而要通过参数获得函数返回值只能通过指针,所以出现了 void** retval。

 

3) 伪代码:void* 执行函数(void *参数){...pthread_exit(要返回的参数);}

main(){

void* status;

rc = pthread_create(&线程ID, NULL, 执行函数, (void*)要输入的参数);

rc = pthread_join(线程ID, &status); //status时,用(long)status即可。

}

4) 代码示例:

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

void* PrintHello(void* threadid){
    long id = (long)threadid;
    printf("thread #%ld : it is #%ld ,hello!!!\n", id, id);
    pthread_exit((void*)5);
}

int main(){
    pthread_t threadID;
    int rc;
    void* status;
    
    rc = pthread_create(&threadID, NULL, PrintHello, (void*)1);
    if(rc){
        perror("create thread error\n");
        exit(-1);
    }
    
    rc = pthread_join(threadID, &status);
    if(rc){
        perror("join thread error\n");
        exit(-1);
    }
    printf("main thread : return value = %ld\n", (long)status);
    pthread_exit(NULL);
    return 0;    
}

运行结果:

 

 3.线程属性

之前使用create()函数时,第二个参数属性对象att总是使用NULL,其实是有实际作用的。线程是有属性的,上文说到的分离线程和非分离线程就是线程的属性之一,称为线程状态。线程的基本属性包括:线程状态,调整策略和栈大小。

1) 属性对象arr

初始化属性对象:int pthread_attr_init(pthread_attr_t *attr);

        arrt是属性对象,成功返回0,失败返回非零错误码。

销毁属性对象:int pthread_attr_destroy(pthread_attr_t *attr);

        arrt是属性对象,成功返回0,失败返回非零错误码。

2) 线程状态

获取线程状态:int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

        arrt为属性对象,detachstate是所获取状态值的指针。

设置线程状态:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

        arrt为属性对象,detachstate是要设置的状态值。

3) 线程栈

获取线程栈:int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

        arrt为属性对象,stacksize 是保存所获取栈大小的指针。

设置线程栈:int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

        arrt为属性对象,stacksize 是要设置的栈大小。

4) 属性对象使用伪代码:

int main(){

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_getstacksize(&attr, stacksize);

pthread_create(&thread, &attr, 执行函数, (void *)arg);

pthread_attr_destroy(&attr);

}

5) 代码实例:代码中涉及到getopt(),calloc(),strdup()等函数需要注意。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <ctype.h>

//extern char* optarg;
//extern int optind;
//extern int opterr;
//extern int optopt;
struct thread_info{
    pthread_t pthreadID;
    int number_id;
    char* argv_string;
};

void* fun(void* var){
    char* p;
    struct thread_info* thread_point = var;
    static char* uargv;
    printf("Thread #%d : top of stack near %p; argv_string = %s\n", 
           thread_point->number_id, &p, thread_point->argv_string);
    uargv = strdup(thread_point->argv_string);
    p = uargv;
    while(*p != '\0'){    
        *p = toupper(*p);
        p++;
    }
    return(uargv);
}

int main(int argc, char* argv[]){
    
    int ch, thread_num;
    int stack_size = -1;
    int ret;
    pthread_attr_t attr;
    void* status;
    struct thread_info* threads = NULL;
    
    while((ch=getopt(argc, argv, "s:")) != -1){
        switch(ch){
            case 's':
                printf("HAVE option : -s\n");
                stack_size = strtoul(optarg, NULL, 0);
                printf("thread's stack size = %d\n", stack_size);
                break;
            default:
                fprintf(stderr, "Usage:%s [-s stack-size] arg...\n", argv[0]);
                exit(-1);
                break;
        }
    }
    thread_num = argc - optind;
    printf("thread_num = %d\n", thread_num);
    
    
    ret = pthread_attr_init(&attr);
    if(ret){
        perror("init attr error\n");
        exit(-1);
    }
    
    
    if(stack_size > 0){
        ret = pthread_attr_setstacksize(&attr, stack_size);
        if(ret){
            perror("set stack size error\n");
            exit(-1);
        }
    }
    else{
        perror("stack size < 0\n");
        exit(-1);
    }
    
    
    threads = calloc(thread_num, sizeof(struct thread_info));
    if(threads == NULL){
        perror("calloc error\n");
        exit(-1);
    }
    
    for(int i=0; i<thread_num; i++){
        threads[i].number_id = i;
        threads[i].argv_string = argv[optind + i];
        ret = pthread_create(&threads[i].pthreadID, &attr, fun, (void*)&threads[i]);
        if(ret){
            perror("create thread error\n");
            exit(-1);
        }
    }
    pthread_attr_destroy(&attr);
    
    for(int i=0; i<thread_num; i++){
        ret = pthread_join(threads[i].pthreadID, &status);
        if(ret){
            perror("join thread error\n");
            exit(-1);
        }
        printf("Join with thread #%d; return value was %s \n", 
               threads[i].number_id, (char*)status);
    }
    
    pthread_exit(NULL);
    free(threads);
    return 0;
}

代码稍微有点长,实际上是有一条脉络下来的。首先代码的目的是:在运行程序时用-s选项输入线程堆栈的大小,接着输入其他字符串参数。然后输出结果是字符串参数的大写形式。要实现这个目标首先一点就是要创建线程create(),创建线程需要线程ID,线程属性对象,线程执行函数,执行函数输入参数。线程ID通过变量声明得到,线程属性对象也是变量声明+初始化得到,但是要修改栈大小。栈大小从-s选项来,因而需要getopt()函数解析选项。得到栈大小之后需要调用pthread_attr_setstacksize()函数设置栈大小。属性对象得到之后,接下来就是编写执行函数,获取执行函数的输入参数。这样整个代码就写完了。

需要注意的是,在调用pthread_attr_setstacksize()函数设置栈大小之前,必须先初始化属性对象。-s输入栈大小时,栈大小不能小于16K

运行结果:

 

 4.互斥量

程序中有一些代码一次只能由一个线程访问,因此要对这些代码进行保护,这些代码叫临界区代码,临界区代码必须以互斥的方式访问。互斥量(也叫互斥锁),是一种用来保护临界区的特殊变量,有锁定和解锁两种状态,当互斥量处于锁定状态时,说明某个线程正在持有这个互斥量,其他线程想要对这个互斥量加锁的时候,将会阻塞,直到持有互斥量的线程解锁这个互斥量。互斥量常用来做临界区保护和代码同步。

1) 互斥量创建与销毁

创建方式①:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;(常用)

创建方式②:pthread_mutex_t mutex

      int pthread_mutex_init(pthread_mutex_t *restrict mutex, constpthread_mutexattr_t *restrict attr);

销毁互斥量:int pthread_mutex_destroy(pthread_mutex_t *mutex);

 

2) 互斥量加锁和解锁

加锁:int pthread_mutex_lock(pthread_mutex_t *mutex);

   int pthread_mutex_trylock(pthread_mutex_t *mutex);(尝试加锁,无法加锁也不会阻塞)

解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

3) 互斥量使用伪代码:

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&mylock);

  临界区代码

pthread_mutex_unlock(&mylock);

 

4) 代码示例:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <pthread.h>
 5 
 6 pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
 7 
 8 void* doPrint(void* var){
 9     
10     long id = (long)var;
11     
12     pthread_mutex_lock(&mylock);
13     for(int i=0; i<5; i++){
14         printf("thread #%ld : it's me #%ld!!\n", id, id);
15         usleep(100);
16     }
17     pthread_mutex_unlock(&mylock);
18     pthread_exit(NULL);
19 }
20 
21 int main(){
22     
23     pthread_t pthreadID[3];
24     int ret;
25     for(long i=0; i<3; i++){
26         ret = pthread_create(&pthreadID[i], NULL, doPrint, (void*)i);
27         if(ret){
28             perror("create thread error\n");
29             exit(-1);
30         }
31     }
32     pthread_exit(NULL);
33     return 0;
34 }

使用了互斥锁后,每个线程都执行结束之后再执行下一个线程,输出是整齐的,如下图:

 

假设将代码的第12行和第17行注释掉,输出变得参差不齐了,如下:

 

5) 死锁现象以及避免死锁

死锁现象指的是:两个或两个以上的线程在执行过程中,因争夺资源而造成互相等待的情况。例如,线程A已经对互斥锁a加锁,而且想要对互斥锁b加锁。线程B已经对互斥锁b加锁,而且想要对互斥锁a加锁。那就会导致,线程A阻塞等待线程B解锁互斥锁b,线程B阻塞等待线程A解锁互斥锁a,但是两个线程都已经阻塞,无法解锁任何一个互斥锁,所以线程A和线程B就死锁了。

避免死锁:加锁应按照一定的顺序进行加锁。比如上面的例子中,加锁顺序为互斥锁a->互斥锁b。线程A已经对互斥锁a加锁,想要对互斥锁b加锁,这符合加锁顺序。而线程B此时不应该处于已经对互斥锁b加锁,而想要对互斥锁a加锁的状态,这是不符合加锁顺序的。正确的加锁顺序应该是先对互斥锁a加锁,再对互斥锁b加锁。

5.条件变量

条件变量也是程序同步的一种。与互斥锁不同,条件变量是通过 等待-通知 的方式进行同步的,同步方式比较高效。实际上使用条件变量要有互斥锁的前提。

1) 创建和销毁

静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;(常用)

动态初始化:pthread_cond_t cond;

      int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

销毁:int pthread_cond_destroy(pthread_cond_t *cond);

 

2) 等待与通知

等待:int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

   int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);(阻塞一段时间后继续运行)

通知:int pthread_cond_signal(pthread_cond_t *cond);(唤醒一个等待线程)

    int pthread_cond_broadcast(pthread_cond_t *cond);(唤醒所有等待线程)

 

3) 条件变量使用伪代码:

线程A(等待)

线程B(通知)

pthread_mutex_lock(&mutex)

while(a < b)

pthread_cond_wait(&cond, &mutex)

pthread_mutex_unlock(&mutex)

if(a<b)

pthread_cond_signal(&cond)

 

4) 代码示例:

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

pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mycond = PTHREAD_COND_INITIALIZER;
long sum = 0;

void* fun12(void* var){
    long id = (long)var;
    
    for(int i=0; i<60; i++){
        usleep(1);
        pthread_mutex_lock(&mylock);
        sum++;
        printf("thread #%ld : sum + 1, \t sum = %ld\n", id, sum);
        pthread_mutex_unlock(&mylock);
        if(sum>100){
            pthread_cond_signal(&mycond);
        }
    }
    pthread_exit(NULL);
}

void* fun3(void* var){
    long id = (long)var;
    
    pthread_mutex_lock(&mylock);
    while(sum < 100)
        pthread_cond_wait(&mycond, &mylock);
    sum = 0;
    printf("thread #%ld : sum alread > 100, \t clear sum\n", id);
    pthread_mutex_unlock(&mylock);
    pthread_exit(NULL);
}

int main(){

    pthread_t pthreadID[3];
    int ret;
    for(long i=0;i<3;i++){
        if(i<2)
            ret = pthread_create(&pthreadID[i], NULL, fun12, (void*)i);
        else
            ret = pthread_create(&pthreadID[i], NULL, fun3, (void*)i);
        
        if(ret){
            perror("create thread error!!\n");
            exit(-1);
        }
    }

    pthread_exit(NULL);
    return 0;
}

线程0和线程1sum进行+1,并且检测sum是否超过100,如果超过100即通知线程3继续运行。运行结果如下:

 

这样做的好处是,线程3只需要运行1次,而不用反复运行线程3检测sum是否超过100

猜你喜欢

转载自www.cnblogs.com/liangda/p/9992676.html
今日推荐