Thread and Peocess
Thread and Peocess
pthread_create()
原型:
int pthread_create(pthread_t* thread, pthread_attr_t* attr, void*(*start_routine)(void* ), void* args){};
其中:
thread: 标识一个线程,是一个pthread_t的变量,
typedef unsigned long int pthread_t
attr: 用于设置线程的属性.这里设为空,采用默认的属性
start_routine: 当线程的资源分配成功后,线程中运行的单元,(函数指针?类似于Java中的Runnable?估计可以是代码块或者是函数.)
arg: 线程函数运行时传入的参数,可以传入控制线程的变量.
成功创建线程则函数返回0,否则返回一个error,常见的error为:
EAGAIN: 表示系统中的线程数量达到了一个上限,
EINVAL: 表示线程的属性非法.
线程创建成功后,新创建的线程按照参数start_routine和args确定一个运行函数,原来的线程在线程创建函数返回后继续运行下一段代码.
pthread_join() && pthread_exit()
pthread_join()等待线程运行结束,是阻塞函数,一直到被等待的线程结束,函数返回并回收被等待线程的资源,函数原型为:
extern int pthread_join __p ((pthread_t __th, void ** __thread_return));
__th: 线程的标识符,即pthread_create()创建成功的值,
__thread_return: 线程返回值,是一个指针,用来存储被等待线程的返回值,线程可以返回一个指针.而这个参数是一个指向指针的指针,所以调用的时候通常是传递一个指针的地址.
一般来说可以先建立返回类型的指针,如int类型的指针,调用pthread_join(pt, (void*)&ret_join),就可以得到ret_join.
线程的属性
线程的属性结构
线程的属性结构体为pthread_attr_t, 在头文件<pthreadtypes.h>
中:
typedef struct __pthread_attr_s
{
int __detachstate; /**线程的终止状态*/
int __schedpolicy; /**调度优先级*/
struct __sched_param __schedparam; /**参数*/
int __inheritsched; /**继承调度*/
int __scope; /**范围*/
size_t __guardsize; /**保证尺寸*/
int __stackaddr_set; /**运行栈*/
void *__stackaddr; /**运行栈地址*/
size_t __stacksize; /**线程运行大小*/
}pthread_attr_t
线程的属性不能直接设置,必须调用参数,常用的设置如线程的摘取状态,调度优先级,运行栈地址和大小,优先级等.而线程的初始化pthread_attr_init(),必须在create()前调用.
如线程的优先级状态由pthread_attr_getschedparam(),pthread_attr_setschedparam()两个函数设置,需要先get,写入,然后set.
#include <sched.h>
#include <pthread.h>
#include <stdio.h>
pthread_attr_t attr;
pthread_t pt;
struct sched_param sch;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, &sch);
sch.sched_priority = 256;
pthread_attr_setschedparam(&attr, &sch);
pthread_create(&pt, &attr, (void*)start_routine, &run);
线程的绑定状态,pthread_attr_setscope(),只有两个选择PTHREAD_SCOPE_SYSTEM, PTHREAD_SCOPE_PROCESS(非绑定).
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
线程的分离状态,决定线程的终止方法,有分离和非分离两种.默认为非分离,在非分离状态下,线程只有遇到pthread_join()才是线程的结束,并释放资源;而分离线程,不需要等待其他线程,自身运行完就结束,马上释放资源.
设置函数为pthread_attr_setdetachstate():
int pthread_attr_setdetachstate(pthread_attr *attr, int detachstate);
detachstate有两个选择,PTHREAD_CREATE_DETACHED对应分离线程,PTHREAD_CREATE_JOINABLE对应非分离模式.
需要注意的是,如果线程运行的相当快,设置分离模式后,可能在pthread_create()返回前就执行完毕,因此有可能再次使用create()返回的线程号会出错.
(mutex 和 信号量 晚上补充 )
文件操作
文件描述符
Linux中每一个设备文件和普通文件,都对应一个整数型的文件描述符,所有对文件的操作都可以通过文件描述符实现.同时文件描述符也是内核空间和用户空间对文件和设备操作的接口.注意:文件描述符仅在一个进程内有效,即不同进程中的同一个设备或者文件可以对应不同的文件描述符,反之亦可.
Linux下有三个已经分配的文件描述符,也可以认为是系统保留的文件描述符,分别是标准输入,标准输出,标准错误;对应数字0,1,2. 所以很多时候重定向为0,1,2时,是传递的标准IO.
Open( ) & Create( )
两者都是成功应返回一个文件描述符,原型如下:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
使用时,应当包含的头文件为sys/types.h, sys/stat.h, fcntl.h
pathname: 是一个字符串变量,该变量的值在不同的系统下面不同,但是通常为1024字节.而如果给出的路径长度大于该数值,系统会对字符串截断,仅选择前面的部分执行.
flag: 是设置打开文件后允许对文件操作的方式.打开文件时必须是只读(O_RDONLY),只写(O_WRONLY),读写(O_RDWR)三种状态的一种,且通常定义为0,1,2.
除了必须设定的三种读写模式之外,还可以设置一些可选的参数项,如:
O_APPEND: 每次对文件的写入都追加到文件的末尾.
O_CREAT: 如果没有文件,就创建文件,且使用此选项就必须设置第三参数mode,作为新创建的文件的权限.
O_EXCL: 查看文件是否存在,如果同时指定了O_CREAT,而且文件已经存在,会返回错误,用这种方式可以安全打开一个文件.
O_TRUNC: 将文件的长度截断为0,如果文件存在,且文件成功打开,则会将其长度截断为0.通常是对文件进行清零操作的时候,选用这个参数.
O_NONBLOCK: 设置文件打开为非阻塞形式,默认为阻塞形式,即每次读写都需要返回状态.
mode就是用户,组成员,其他成员分别对文件的可读,可写,可执行的排列组合,具体可以百度.
一个O_CREAT和O_EXCL的容错的文件读写例子:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(void){
int fd = -1;
char filename[] = "test.txt";
/**打开文件,如果文件存在,则报错*/
fd = open(filename, O_RDWR|O_CREAT|O_EXCL,S_IRWXU);
if(fd == -1){
/**文件已经存在,重新打开*/
fd = open(filename, O_RDWR);
}else{
/**文件不存在,创建并打开*/
}
return 0;
}
create()函数近似与open()的精简版本,原型为:
int create(const char* pathname, mode_t mode)
也可以近似理解为:
open(pathname, O_WRONLY|O_TRUNC, mode)
close()
原型为:
int close(int fd);
如果不关闭文件,系统在进程退出时,会自动关闭文件,但是不会收回文件描述符的值,这样在长时间后会因为达到文件描述符的最大上限而出现文件打开错误.
read()
read()从打开文件中读取数据,需要加入unistd.h,原型为:
ssize_t read(int fd, void* buf, size_t count);
从文件描述符中读取count字节,放到buf开始的缓冲区,count为0,读取为0,count大于SSIZE_MAX,结果不可预料.读取成功后,文件对应的读取位置指针向后偏移,大小为成功读取的字节数.
返回0说明达到文件末尾,返回-1代表读取错误,ssize_t可以定义为long或者为int.
write()
write()写入文件,其余参数同read().如果打开或者创建的文件带有O_APPEND属性,则每次写操作之前会将写操作的位置移动到文件的末尾.原型为:
ssize_t
(write 以及之后的问题暂时不补充)
进程间的通信
普通的半双工管道
在shell中使用 '|' 来标识,而在程序中用pipe(int[])来得到.
pipe是一个单向的IPC机制,限制有两点:
- 在普通的匿名管道中有缓冲区的限制,一般是在include/Linux/limits.h中定义,PIPE_BUF,
POSIX.1定义该值不得小于512字节. - 管道必须建立在父子进程或者是兄弟进程之间,不能在任意的两个进程中建立.
注意: 管道的阻塞性和原子性
对于一次写入管道内的数据小于128K,则是非阻塞的,当大于128K时,会阻塞直到读完成才会返回.并且在低于512的情况下,管道的读写是原子性的,而大于该值,多个线程向pipe写会造成数据覆盖.
int fd[2];
int status = pipe(fd);
这里的fd中,fd[0]是读的一端的文件描述符,fd[1]是写的一端的文件描述符,而如果真的需要通过pipe进行双工通信则必须建立两个管道.
通常需要在两个线程中分别关闭一个文件描述符.(主要是因为两个进程中都拿到了fd[],所以最好拿到即关掉不属于自己这端的文件描述符)
int *write_fd = &fd[1];
int *read_fd = &fd[0];
pid_t pid;
pid = fork();
if(pid == -1){
printf("fork error \n");
}else if(pid == 0){
/*in the child process*/
close(*read_fd);
write(*write_fd, write_thing, write_length);
}else if(pid > 0){
/*in the parent process*/
close(*write_fd);
read(*read_fd, readbuffer, readbuffe_size);
}
这里还需要注意一点,必须先调用pipe()再调用fork(),否则子进程中无法继承得到文件描述符.