Linux系统编程多线程技术

线程
    线程是轻量级的代码并行技术;线程不需要复制进程的资源,而是直接共享进程的资源;
    线程只需要一个额外的栈就可以了;因此很多应用都是使用多线程技术;
    主流的操作系统支持多进程,每个进程的内部支持多线程,线程可以嵌套;
    JAVA语言中没有进程概念,因为Java本身是运行在虚拟机中的,而虚拟机本身就是一个进程;
    大多数应用程序都是使用多线程;

如何实现代码并行?
    程序代码执行至少需要CPU(中央处理器)和内存,代码的并行就需要多个CPU和多段内存;
    内存可分段,CPU可分时间片;
    大多系统采用CPU时间片实现代码的并行(伪并行);首先把CPU的运行时间分成极小的CPU时间片,然后每个线程拿一片,有时间片的线程就有运行的权利,但先后次序是完全无序的;一轮之后再重新分配CPU时间片;
    人的感官是需要时间的,假定人的视觉需要0.1s, 0.1s = 100 ms,此时分1 ms做一个CPU时间片,有4个线程A B C D,每个线程先拿1 ms的CPU时间片,当我们看到四个线程时,每个线程都运行了25 ms;
    多线程之间是乱序运行,但每个线程内部都是顺序执行;
    线程和线程之间是互相独立,但又互有影响(资源有共享);
    多线程可以提升程序的效率;如多线程下载工具;
    
    每个进程的内部至少有一个线程,就是main(),叫主线程;
    主线程一旦结束,进程就随之结束,其他线程就随着进程的结束而结束;

Unix有哪些关于线程的API(Application Program Interface应用编程接口);
    多线程的开发定义在POSIX规范中,使用pthread.h头文件;
    所有的函数放在libpthread.so共享库文件中;编译和链接要使用-pthread选项或-lpthread;双L连接法,L和l在此可以省略;
    函数基本上都是以pthread_开头的;
    pthread即process中的thread;线程是隶属于进程的;

创建线程的函数
    pthread_create()
int pthread_create(//参数4个针(全是指针);
        pthread_t *id, //线程ID;
        const pthread_attr_t *attr, //线程属性,一般给0(默认);
        void *(*task) (void *), //函数指针
        void *argue //传给函数指针所调用函数需要使用的参数,无参数用0
    );
    参数id用于存储线程ID,每个线程用ID做标识;
    attr是线程属性,默认给0即可;
    task是函数指针,参数和返回值都可以是任意类型,没有限制;
    argue是task的参数,函数指针只能传函数地址,而参数用argue传入;
    成功返回0,失败返回错误码;
    注意线程的函数基本都返回错误码,而不使用errno(errno是全局变量,会相互覆盖,不能确定是谁改变的),因此处理线程的错误要使用strerror(),不能使用perror();
    On success, pthread_create() returns 0;
    on error, it returns an error number, and the contents of *thread are undefined;
    C语言用外部的全局变量errno(error number)记录错误信息;错误信息分为错误编号(int)和具体信息(字符串);每个错误编号都对应一个具体信息;errno存储的是错误编号;
    函数strerror()/perror()/printf()用于错误信息的显示和转换;
    strerror() //传入一个编号,返回具体信息(转换函数);
    perror() //不用传入错误编号,直接打印errno对应的信息(会自动换行);
    printf("%m") //直接打印errno对应的信息;
    不是所有的函数都使用errno处理处理错误,比如线程的函数;

pthread_self()函数可以取得当前线程的线程ID;
/*
 * 线程创建练习
 */
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void *task(void *p) {
    int i = 0;
    for (i = 0; i <= 50; i++) {
        printf("task:%02d\t", i);
    }
    printf("\n");
}
int main() {
    pthread_t id, mid;
    /* 启动一个线程,执行task()函数; */
    int res = pthread_create(&id, 0, task, 0); //不会阻塞
    /* 不会等待线程的运行或结束 */
    if (res) {
        printf("%s\n", strerror(res));
    }
    mid = pthread_self();    //取当前线程的ID;
    printf("新启动线程id:%u,当前线程id:%u\n", id, mid);
    /* 线程的id比较大,需要用%u匹配; */
    int i = 0;
    for (i = 0; i <= 50; i++) {
        printf("M***:%02d\t", i);
    }
    printf("\n");
    usleep(100000);        //主线程结束,进程结束;
    /* 总闸关闭,所有的小开关都失效了 */
    return 0;
}

/*
 * 线程传参与资源共享;
 */
#include <stdio.h>
#include <pthread.h>
void *task(void *p) {
    int *pi = (int *)p;
    printf("*pi = %d\n", *pi);
    *pi = 200;
}
int main() {
    pthread_t id;
    int x = 100;
    pthread_create(&id, 0, task, &x);
    sleep(1);
    pthread_join(id, NULL); //可以等待线程(id)的结束;
    printf("x = %d\n", x); //200
    /* 线程不复制进程的资源,直接共享进程的资源; */
    return 0;
}


    在创建线程时,传参是传递地址,但直接传递整数(指针的本质也是整数)也是可行的;
    在传地址时,注意保证地址是有效的;空指针/野指针/已经被free()的堆区指针都是无效的;
/*
 * 线程传参练习
 */
#include <stdio.h>
#include <string.h>
#include <pthread.h>
/* 练习写一个线程,传入半径,计算圆的面积; */
void *task1(void *p) {
    double r = *(double *)p;
    printf("圆面积:%lg\n", 3.14 * r * r);
}
void *task2(void *p) {
    /* 特殊用法,把整数当指针传入,然后再把指针当整数用 */
    /* 拿指针当整数来用 */
    int i = (int)p;
    printf("i = %d\n", i);
}
int main() {
    pthread_t id1, id2;
    double r;
    printf("please input r:\n");
    scanf("%lf", &r);
    pthread_create(&id1, 0, task1, &r);
    /* pthread_join()可以让一个线程等待另外一个线程结束 */
    pthread_join(id1, NULL);
    int x = 100;
    /*
     * int *px;//野指针不可以做参数传入
     * //结果不确定或段错误
     * pthread_create(&id2, 0, task2, px);
     */
    pthread_create(&id2, 0, task2, (void *)x);
    pthread_join(id2, NULL);
    printf("x = %d\n", x);
    return 0;
}


pthread_join()函数可以让一个线程等待另外一个线程;
比如在线程a中调用了pthread_join(b, NULL),线程a会等待线程b结束;
线程没有父子线程之说;

线程可以有返回值,返回值由pthread_join()函数的第二个参数带回;
线程返回值类型是 void *,因此参数的类型就是 void **类型;
int pthread_join(pthread_t id, void **retval);
/*
 * pthread_join()取线程的返回值
 */
#include <stdio.h>
#include <pthread.h>
#include <string.h>
/*
 * char *get() {
 *     //return "abc";//可以,返回一个有效的地址;
 *     char *s1 = "abc";    ///返回指向字面值的指针可以;
 *     return s1;
 *     //char s2[] = "abc";//指向栈区的指针,不可以;
 *     //return s2;
 *     //返回值类型可以是指针;不能是数组,不能确定大小;
 * }
 * int *test() {
 *     //int x = 100; return &x; //不可以返回局部变量的地址;
 *     static int x = 100;
 *     return &x;
 * }
 */
void *task1(void *p) {
    static int sum = 0;
    int i = 0;
    for (i = 1; i <= 10; i++) {
        sum += i;
    }
    return ∑ //pp = ∑//将返回值&num给pp带回
}
void *task2(void *p) {
    int sum = 0;
    int i = 0;
    for (i = 1; i <= 100; i++) {
        sum += i;
    }
    return (void*)sum;
}
int main() {
    pthread_t id1, id2;
    pthread_create(&id1, 0, task1, NULL);
    int *p;
    pthread_join(id1, (void **)&p);    //p不再是野指针
    //p是一级指针,&p是二级指针;join函数中要转换成void**;
    //只要保持p和函数return后面类型一致即可;
    printf("sum is %d\n", *p);
    int num;
    pthread_create(&id2, 0, task2, NULL);
    pthread_join(id2, (void **)&num);
    printf("sum is %d\n", num);
    /*
     * printf("可以取到%s\n", get());
     * int *pi = test();
     * printf("可以取到%d\n", *pi);
     */
    return 0;
}


关于返回值的一些常识
    1-> 返回值类型不能是数组,但可以是指针;
    2-> 可以返回局部变量,但不能返回指向局部变量的地址;
    3-> static 修饰的局部变量地址可以返回;//在全局区;

线程的退出
    1.正常退出
        在线程的函数中执行了return;语句;
        执行了void pthread_exit(void* retval)函数;
    2.非正常退出
        被其他线程用int pthread_cancel(pthread_t thread)取消;
        自身运行出了问题;
    注:exit()退出的是进程,不能用于退出线程;否则所有线程全结束;
    信号退出的是进程,也不能用于退出线程;
/*
 * pthread_exit()
 */
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void *task1(void *p) {
    int i = 0;
    while (++i) {
        printf("1");
        if (i >= 1000) {
            pthread_exit((void*)i);
            /* 退出进程并返回指针的值; */
        }
    }
}
void *task2(void *p) {
    while (1) printf("2");
}
int main() {
    pthread_t id1, id2;
    pthread_create(&id1, 0, task1, NULL);
    pthread_create(&id2, 0, task2, NULL);
    int *p;
    pthread_join(id1, (void **)&p);
    printf("thread 1 return: %d\n", p);
    if (!pthread_cancel(id2)) {
        /* 取消一个线程的执行,成功结束返回0 */
        printf("thread 2 has been canceled\n");
    }
    return 0;
}


int pthread_join(pthread_t thread, void ** rval_ptr);
功能
    调用这将挂起并等待新进程终止;
    当新线程调用pthread_exit()退出或者return时,进程中的其他线程可通过pthread_join()获得进程的提出状态;
使用约束
    一个新线程仅仅允许一个线程使用该函数等待它终止;
    被等待线程应处于可join状态,即非DETACHED状态;
返回值
    成功返回0,失败返回错误码;
说明
    类似于waitpid()函数;


线程执行轨迹
    同步方式(非分离状态)
        等待新创建线程结束;
        只有当pthread_join()函数返回时,创建的线程才终止,才可释放自己占用的系统资源;
    异步方式(分离状态)
        未被其他线程等待,自己运行结束即可终止线程并释放系统资源(自爆);
        返回值是无法被pthread_join()拿到的;
    还有一种既不pthread_join()也不分离的,没人回收;

线程的状态:分离和非分离
线程同步技术:互斥量/互斥锁/信号量;

线程虽然共享了资源,但还是有自己的栈区内存;
线程资源的回收有以下三种可能
    1.非分离的线程:调用了pthread_join(),资源在pthread_join()函数结束时立即回收;(有人监护);
    2.分离的线程;线程一结束立即回收资源;(不设监护人,自觉型的);
    3.非分离的线程,也没有被pthread_join();资源不一定何时回收;应尽量避免;(没人看护,也不够自觉);

    因此,线程最好处于分离状态或被pthread_join(),资源才会正常回收;
    如果把线程设置为join状态,只需在创建线程以后调用pthread_join()即可;
    如果把线程设置为分离状态,只需在创建线程以后调用pthread_detach(id)即可;处于detach状态的线程pthread_join()函数无效;

/*
 * pthread_detach()函数演示
 */
#include <stdio.h>
#include <pthread.h>
void *task(void *p) {
    int i;
    for (i = 0; i < 10; i++) {
        printf("task:%d\t", i);
        usleep(10000);
    }
}
int main() {
    pthread_t id;
    pthread_create(&id, 0, task, 0);
    pthread_detach(id);
    //分离状态join无效;
    /* pthread_join(id, 0); */
    /* 主线程不会等待id线程结束; */
    int i;
    for (i = 0; i < 10; i++) {
        printf("main:%d\n", i);
        usleep(10000);
    }
    printf("The program over\n");
    return 0;
}

    如果先pthread_join()再pthread_detach()的话,detach是无效的废代码,因为pthread_join()会等待线程结束,这是pthread_join()已经没有意义了;

请继续浏览下一篇,多线程同步技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值