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

一、线程的基础知识
进程和程序的区别
进程和线程的区别:
"线程,是执行的基本单位,线程共享进程的资源"。
(1条线程就是1条执行的基本单位,每个线程有自己独立的栈帧)
进程,是资源分配的基本单位,调度的单位。
进程有自己的pid,线程也有自己的id,称为"tid"。
"一个进程里可以有多个线程"。
每个进程都至少有一个线程,这个线程是进程的主线程。


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




二、线程的创建


"pthread_create"(3)
#include
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
#include
//线程的执行函数
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_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
void pthread_exit(void *retval);
功能:终止线程
参数:"retval" 这个值被调用的pthread_join的线程使用
返回值:void
注意:pthread_exit的"参数不能使用局部变量的地址",局部变量栈里。


"pthread_cancel"(3)
#include
int pthread_cancel(pthread_t thread);
功能:给一个线程发送终止请求
参数:"thread" 指定接收终止请求的线程
返回值:
成功 - 返回 0
失败 - 返回非 0 错误号




四、线程的分离和汇总


线程的"分离":
"pthread_detach"(3)
#include
int pthread_detach(pthread_t thread);
功能:分离一个线程。一个线程终止,资源自动回收,不需要其他线程参与
参数:"thread" 要分离的线程tid
返回值:
成功 - 返回 0
失败 - 返回非 0 错误号


线程的"汇总":
"pthread_join"(3)
#include
int pthread_join(pthread_t thread, void **retval);
功能:等待线程终止,回收线程资源
参数:
"thread" 指定要等待的线程的tid
"retval" 存放thread指定的线程的退出状态码
返回值:
成功 - 返回 0
失败 - 返回非 0 错误号


/** 举例验证:
使用各种线程的退出方式,然后回收线程资源 pthread_join.c **/
#include
#include
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
#include
#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_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
#include
#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_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
#include
#include
#include
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;
}

你可能感兴趣的:(Linux/Unix)