您的位置:首页 > 运维架构 > Linux

Linux系统编程多线程技术

2015-08-25 20:13 441 查看
线程

线程是轻量级的代码并行技术;线程不需要复制进程的资源,而是直接共享进程的资源;

线程只需要一个额外的栈就可以了;因此很多应用都是使用多线程技术;

主流的操作系统支持多进程,每个进程的内部支持多线程,线程可以嵌套;

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()已经没有意义了;

请继续浏览下一篇,多线程同步技术
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: