您的位置:首页 > 编程语言

Unix高级编程:线程基础、线程的创建、退出、分离、汇合、同步问题

2017-01-15 22:57 411 查看
一、线程的基础知识

进程和程序的区别

进程和线程的区别:

"线程,是执行的基本单位,线程共享进程的资源"。

(1条线程就是1条执行的基本单位,每个线程有自己独立的栈帧)

进程,是资源分配的基本单位,调度的单位。

进程有自己的pid,线程也有自己的id,称为"tid"。

"一个进程里可以有多个线程"。

每个进程都至少有一个线程,这个线程是进程的主线程。

需要"多条执行路线"时,使用多进程的话,进程间切换资源开销比较大,原因是需要复制PCB。"使用线程不需要复制PCB,属于进程内的切换,资源开销小"。

二、线程的创建

"pthread_create"(3)

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

                  void *(*start_routine) (void *), void *arg);

Compile and link with "-pthread". /** 编译加上该库 -lpthread **/

功能:创建一个新的线程

参数:

"thread" 将 tid 存放在该参数的变量空间里

"attr" 取 NULL 采用默认的属性

"start_routine" (函数访问方式,返回值void *,参数void *)
新线程执行的函数

"arg" 是start_routine函数的唯一的参数

返回值:

成功 - 返回 0

失败 - 返回一个错误号,thread未定义。

/** 举例验证:
在进程中创建线程,pthread_create.c **/

#include <stdio.h>

#include <pthread.h>

//线程的执行函数

void *handle(void *arg) {

    printf("arg: %s\n", (char *)arg);

    printf("pid: %d \t tid: %lu\n", getpid(), pthread_self());
//两次的pid一样说明是一样

    return arg; //return (void *)1; 或者 return NULL;

}

int main(void) {

    pthread_t tid;

    //创建一个新的线程

    pthread_create(&tid, NULL, handle, "new");

    sleep(1);

    handle("main"); //handle的主线程

    return 0;

}

gcc pthread_create.c -lpthread

查看线程自己的tid:

"pthread_self"(3)

#include <pthread.h>

pthread_t pthread_self(void);

功能:获取线程自己的id,即tid (unsigned long int) "%lu"

参数:void

返回值:

总是成功 - 返回线程的id

三、线程的退出

1. 线程里不能使用使用"exit"(3)退出,可以使用"return"退出
千万不要使用return返回 <局部变量> 的地址

2. 线程里还可以使用"pthread_exit"(3)退出线程,只是退出当前线程。

3. 线程里还可以使用"pthread_cancel"(3)终止某个线程

"pthread_exit"(3)

#include <pthread.h>

void pthread_exit(void *retval);

功能:终止线程

参数:"retval" 这个值被调用的pthread_join的线程使用

返回值:void

注意:pthread_exit的"参数不能使用局部变量的地址",局部变量栈里。

"pthread_cancel"(3)

#include <pthread.h>

int pthread_cancel(pthread_t thread);

功能:给一个线程发送终止请求

参数:"thread" 指定接收终止请求的线程

返回值:

成功 - 返回 0

失败 - 返回非 0 错误号

四、线程的分离和汇总

线程的"分离":

"pthread_detach"(3)

#include <pthread.h>

int pthread_detach(pthread_t thread);

功能:分离一个线程。一个线程终止,资源自动回收,不需要其他线程参与

参数:"thread" 要分离的线程tid

返回值:

成功 - 返回 0

失败 - 返回非 0 错误号

线程的"汇总":

"pthread_join"(3)

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

功能:等待线程终止,回收线程资源

参数:

"thread" 指定要等待的线程的tid

"retval" 存放thread指定的线程的退出状态码

返回值:

成功 - 返回 0

失败 - 返回非 0 错误号

/** 举例验证:
使用各种线程的退出方式,然后回收线程资源 pthread_join.c **/

#include <stdio.h>

#include <pthread.h>

void *handle1(void *arg) {

    printf("thread %s running...\n", (char *)arg); //new

    return (void *)1;

}

void *handle2(void *arg) {

    printf("thread %s running...\n", (char *)arg);//second

    pthread_exit( (void *)2 );

}

void *handle3(void *arg) {

    while(1) {

        printf("thread %s running...\n", (char *)arg);

        sleep(1);

    }   

}

int main(void) {

    pthread_t tid;

    void *ret;

    //创建一个线程

    pthread_create(&tid, NULL, handle1, "new"); //新线程先执行

    //等待线程汇合

    pthread_join(tid, &ret); //void **retval

    printf("new exit code %d\n", (int)ret); //1 

    //创建第二个线程

    pthread_create(&tid, NULL, handle2, "second");

    pthread_join(tid, &ret);

    printf("second exit code %d\n", (int)ret);//2

    //创建第三个线程

    pthread_create(&tid, NULL, handle3, "third");

    //给线程发送终止信息

    pthread_cancel(tid);

    //等待线程汇合

    pthread_join(tid, &ret);

    printf("third exit code %d\n", (int)ret);

    //创建第四个线程

    pthread_create(&tid, NULL, handle2, "fourth");  

    //分离线程,资源自动回收

    pthread_detach(tid);

    handle1("main"); //新线程执行完,主线程执行

    return 0;

}

五、线程的同步

线程创建完毕,多线程和是异步的。异步的线程同时访问临界资源,这时候会出现错误。为了解决这些错误,引进了线程的同步。

/** 举例说明:
多线程异步访问临界资源,出现问题的情况。any_thread.c **/

#include <stdio.h>

#include <pthread.h>

#define NLOOP 5000

int counter;

void *doit(void *arg) {

    int val;

    for(int i = 0; i < NLOOP; i++) {

        val = counter;

        printf("%lu : %d\n", pthread_self(), val+1);//每次5000多

        counter = val+1;

    }

    return NULL;

}

int main(void) {

    pthread_t tidA, tidB;

    //创建两个线程

    pthread_create(&tidA, NULL, doit, NULL);

    pthread_create(&tidB, NULL, doit, NULL);

    pthread_join(tidA, NULL);

    pthread_join(tidB, NULL);

    return 0;

}

"mutex锁"

线程对临界资源访问的时候,上一把锁,上锁成功,才可以访问;上锁不成功,不能访问。

线程访问临界资源的时候,首先要获取一把锁。

使用mutex锁需要使用下列函数:

"pthread_mutex_init"(3)

"pthread_mutex_lock"(3)

"pthread_mutex_trylock"(3)

"pthread_mutex_unlock"(3)

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //初始化锁

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

功能:初始化mutex锁

参数:

"mutex" 要初始化的mutex锁

"mutexattr" NULL 缺省

返回值:

成功 - 总是返回 0

int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:加锁,如果这把锁没有锁上,加锁并改成自己拥有且立即返回;
 如果这把锁由其它线程加锁,那么当前线程就挂起等待,直到该锁被解开,当前线程获取这把锁。

参数:"mutex" 指定要上锁的锁。

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_mutex_trylock(pthread_mutex_t *mutex);

功能:尝试加锁,如果锁没有被另外的线程使用,立即加锁并返回;
 如果被其他线程加锁,立即返回,并将错误码置为 EBUSY 。

参数:"mutex" 尝试加锁的锁

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:解开锁,锁原来是锁着的,还是当前线程所拥有的锁。

参数:"mutex" 要解开的锁

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:销毁锁,并释放mutex持有的资源

参数:"mutex" 要销毁的锁

返回值:

成功 - 返回 0

失败 - 返回错误码

/** 举例验证:
使用mutex锁解决多线程同步会导致错误的问题。mutex.c**/

#include <stdio.h>

#include <pthread.h>

#define NLOOP 5000

//初始化一把mutex锁,全局初始化锁

pthread_mutex_t fmutex = PTHREAD_MUTEX_INITIALIZER;

int counter;

void *doit(void *arg) {

    int val;

    for(int i = 0; i < NLOOP; i++) {

        //加锁,在开始访问全局变量时

        pthread_mutex_lock(&fmutex);

        val = counter;

        printf("%lu : %d\n", pthread_self(), val+1);//每次5000多

        counter = val+1;

        //解锁,在结束访问完全局变量时

        pthread_mutex_unlock(&fmutex);

    }

    return NULL;

}

int main(void) {

    pthread_t tidA, tidB;

    //创建两个线程

    pthread_create(&tidA, NULL, doit, NULL);

    pthread_create(&tidB, NULL, doit, NULL);

    pthread_join(tidA, NULL);

    pthread_join(tidB, NULL);

    //销毁mutex锁

    pthread_mutex_destroy(&fmutex);

    return 0;

}

"条件变量"

线程间同步还有这样一种情况,线程A需要等待某个条件成立才能继续往下执行。现在这个条件不成里,线程A就阻塞等待;而线程B在执行过程中使这个条件成立,就唤醒了线程A,线程A继续执行。这个条件,就称为"条件变量"。

使用条件变量,需要使用以下函数:

"pthread_cond_init"(3)

#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化条件变量

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

功能:初始化一个条件变量

参数:

"cond" 要初始化的条件变量

"cond_attr" NULL,默认属性

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_cond_signal(pthread_cond_t *cond);

功能:通知在cond条件上等待的线程,从中任选一个开始执行

参数:"cond" 指定了条件

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:唤醒了所有在cond条件上等待的线程

参数:"cond" 等待的条件

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

功能:1. 解开mutex锁;2. 阻塞等待;3. 当条件成立的时候被唤醒,重新加锁

参数:

"cond" 阻塞等待的条件

"mutex" 被解开的锁

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

功能:类似于pthread_cond_wait,但是有等待周期

参数:

"cond" 阻塞等待的条件

"mutex" 被解开的锁

"abstime" 指定了等待周期的时间

返回值:

成功 - 返回 0

失败 - 返回错误码

int pthread_cond_destroy(pthread_cond_t *cond);

功能:销毁一个变量,释放资源

参数:"cond" 要销毁的条件变量

返回值:

成功 - 返回 0

失败 - 返回错误码

/** 举例验证:
使用条件变量实现生产者和消费者的例子。
生产者生产一个结构体串在链表的头上,而消费者从头部取走一个结构体消费。 consumer.c **/

#include <stdio.h>

#include <pthread.h>

#include <time.h>

#include <stdlib.h>

typedef struct msg{

    int num;

    struct msg *next;

}msg_t; //共享临界资源:链表

msg_t *head = NULL;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *arg) {

    msg_t *p;

    for(;;) {
//先加锁,开始操作时

        pthread_mutex_lock(&mutex);

        if(head == NULL) { //if == while
//解锁->等待条件成立->再加上锁

            pthread_cond_wait(&cond, &mutex); 

        }
//代表链表非空,才能操作

        p = head;

        head = p->next;
//再解锁,操作完时

        pthread_mutex_unlock(&mutex);

        printf("c %d\n", p->num);

        free(p);

        p = NULL;

        sleep(rand() % 5);

    }   

}

void *producer(void *arg) {

    msg_t *new;

    for(;;) {

        new = (msg_t *)malloc(sizeof(msg_t));

        new->num = rand() % 1000 + 1;

        printf("p %d\n", new->num);
//加锁

        pthread_mutex_lock(&mutex);
//链表操作,增加链表中的数据

        new->next = head;

        head = new;
//解锁

        pthread_mutex_unlock(&mutex);

        pthread_cond_signal(&cond);//通知cond上等待的线程,任选执行

        sleep(rand() % 5); 

    }

    return ;

}

int main(void) {

    srand(time(NULL));

    pthread_t pid, cid;
//创建两个线程,1个用于生产,1个用于消费

    pthread_create(&pid, NULL, producer, NULL); //生产者

    pthread_create(&cid, NULL, consumer, NULL); //消费者
//join等待两个线程汇合,对结束状态不需要关心,所以取NULL    
pthread_join(cid, NULL);

    pthread_join(pid, NULL);

    return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编程 unix