我们知道,进程在各自独立的地址空间中运行,进程之间共享数据需要用进程间通信机制,有些情况需要在一个进程中同时执行多个控制流程,这时候线程就派上了用场,比如实现一个图形界面的下载软件,一方面需要和用户交互,等待和处理用户的鼠标键盘事件,另一方面又需要同时下载多个文件,等待和处理从多个网络主机发来的数据,这些任务都需要一个“等待-处理”的循环,可以用多线程实现,一个线程专门负责与用户交互,另外几个线程每个线程负责和一个网络主机通信。
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
创建一个新的线程
参数
thread:线程ID
attr:设置线程的属性,一般设置为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码;
以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。由于pthread_create的错误码不保存在errno中,因此不能直接用perror(3)打印错误信息,可以先用strerror(3)把错误号转换成错误信息再打印。读取返回值要比读取线程内的errno变量的开销更小!
/** 实践: 新的错误检查与错误退出函数 **/ inline void err_check(const std::string &msg, int retno) { if (retno != 0) err_exit(msg, retno); } inline void err_exit(const std::string &msg, int retno) { std::cerr << msg << ": " << strerror(retno) << endl; exit(EXIT_FAILURE); }pthread_exit
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一个局部变量,因为当其它线程得到这个返回指针时线程函数已经退出了。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1、从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit,而如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止。
2、一个线程可以调用pthread_cancel 终止同一进程中的另一个线程。
3、线程可以调用pthread_exit终止自己。
pthread_joinint pthread_join(pthread_t thread, void **value_ptr);
当pthread_create 中的 start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1、如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
2、如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED。
3、如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。
/** 示例: 等待线程退出 **/ void *thread_rotine(void *args) { for (int i = 0; i < 10; ++i) { printf("B"); fflush(stdout); usleep(20); } pthread_exit(NULL); } int main() { pthread_t thread; int ret = pthread_create(&thread, NULL, thread_rotine, NULL); err_check("pthread_create", ret); for (int i = 0; i < 10; ++i) { printf("A"); fflush(stdout); usleep(20); } ret = pthread_join(thread, NULL); err_check("pthread_join", ret); putchar('\n'); return 0; }pthread_self
pthread_t pthread_self(void);返回线程ID
在Linux上,pthread_t类型是一个地址值,属于同一进程的多个线程调用getpid(2)可以得到相同的进程号,而调用pthread_self(3)得到的线程号各不相同。线程id只在当前进程中保证是唯一的,在不同的系统中pthread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印。
/** 示例:主控线程与子线程传递数据 **/ typedef struct _Student { char name[20]; unsigned int age; } Student; void *threadFunction(void *args) { cout << "In Thread: " << pthread_self() << endl; Student tmp = *(Student *)(args); cout << "Name: " << tmp.name << endl; cout << "Age: " << tmp.age << endl; pthread_exit(NULL); } int main() { Student student = {"tach",22}; pthread_t thread; //启动创建并启动线程 pthread_create(&thread,NULL,threadFunction,&student); //等待线程结束 pthread_join(thread,NULL); return 0; }pthread_cancel
int pthread_cancel(pthread_t thread);线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定 。
pthread_detach
int pthread_detach(pthread_t thread);
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止(僵线程)。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。对一个尚未detach的线程调用pthread_join或pthread_detach都可以把该线程置为detach状态,也就是说,不能对同一线程调用两次pthread_join,或者如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
这个函数既可以在主线程中调用,也可以在thread_function里面调用。
总结:进程 VS. 线程
进程(pid_t) |
线程(pthread_t) |
Fork |
Pthread_create |
Waitpit |
Pthread_join/Pthread_detach |
Kill |
Pthread_cancel |
Pid |
Pthead_self |
Exit/return |
Pthread_exit/return |
僵尸进程(没有调用wait/waitpid等函数) |
僵尸线程(没有调用pthread_join/pthread_detach) |
/** 将并发echo server改造成多线程形式 **/ void echo_server(int clientSocket); void *thread_routine(void *arg); int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); if (sockfd == -1) err_exit("socket error"); int optval = 1; if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) == -1) err_exit("setsockopt error"); struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8002); serverAddr.sin_addr.s_addr = INADDR_ANY; //绑定本机的任意一个IP地址 if (bind(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1) err_exit("bind error"); if (listen(sockfd,SOMAXCONN) == -1) err_exit("listen error"); while (true) { int peerSockfd = accept(sockfd, NULL, NULL); if (peerSockfd == -1) err_exit("accept error"); pthread_t tid; /**注意: 下面这种用法可能会产生问题 当另一个连接快读快速到达, peerSockfd的内容更改, 新创建的线程尚未将该值取走时,线程读取的就不是 我们原来想让线程读取的值了 int ret = pthread_create(&tid, NULL, thread_routine, (void *)&peerSockfd); **/ //解决方案: 为每一个链接创建一块内存 ,注意之后要释放 int *p = new int(peerSockfd); int ret = pthread_create(&tid, NULL, thread_routine, p); if (ret != 0) err_thread("pthread_create error", ret); } close(sockfd); }
void *thread_routine(void *args) { //将线程设置分离状态, 避免出现僵尸线程 pthread_detach(pthread_self()); int peerSockfd = *(int *)args; //注意函数中指针取出之后记得将内存释放掉 delete (int *)args; echo_server(peerSockfd); cout << "thread " << pthread_self() << " exiting ..." << endl; pthread_exit(NULL); } void echo_server(int clientSocket) { char buf[BUFSIZ] = {0}; int readBytes; while ((readBytes = read(clientSocket, buf, sizeof(buf))) >= 0) { if (readBytes == 0) { cerr << "client connect closed" << endl; break; } if (write(clientSocket, buf, readBytes) == -1) { cerr << "server thread write error" << endl; break; } cout << buf; bzero(buf, sizeof(buf)); } }