1. 多线程常用函数
1.1 创建一条新线程pthread_create
对此函数使用注意以下几点:
- 线程例程指的是:如果线程创建成功,则该线程会立即执行的函数。
- POSIX线程库的所有API对返回值的处理原则一致:成功返回0,失败返回错误码errno.
- 线程属性如果为NULL, 则会创建一个标准属性的线程,线程的属性非常多,有关线程的属性待研究
PTHREAD_CREATE_DETACHED 分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
PTHREAD _CREATE_JOINABLE 线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算。
1.2 线程的退出
线程跟进程类似,在缺省的状态下退出之后,会变成僵尸线程,并且保留退出值。其他线程可以通过相关 API 接合该线程——使其资源被系统回收,如果愿意的话还可以顺便获取其退出值。下面是相关 API:
线程执行完后如果不join的话,线程的资源会一直得不到释放而导致内存泄漏。
用上述函数需要注意以下几点:
- 如果线程退出时没有退出值,那么 retval 可以指定为 NULL。
- pthread_join( )指定的线程如果尚在运行,那么他将会阻塞等待。
- pthread_tryjoin_np( )指定的线程如果尚在运行,那么他将会立即出错返回。
在某个时刻不能等某个线程“自然死亡”,而需要勒令其马上结束,此时可
以给线程发送一个取消请求,让其中断执行而退出。用到如下 API:
1.3 pthread_join/pthread_exit的用法解析
pthread_join
用于等待一个线程的结束,也就是主线程中要是加了这段代码,就会在加代码的位置卡主,直到这个线程执行完毕才往下走。
pthread_exit
用于强制退出一个线程(非执行完毕退出),一般用于线程内部。
一般都是 pthread_exit
在线程内退出,然后返回一个值。这个时候就跳到主线程的 pthread_join
了(因为一直在等你结束),这个返回值会直接送到pthread_join
,实现了主与分线程的通信。
2. 线程资源回收pthread_detach()函数的使用
每一个线程在任何情况,要么是可结合的状态(joinable),要么是可分离的状态(detached)。
两个函数的原型:
int pthread_join(pthread_t tid, void ** pthread_return);
int pthread_detach(pthread_t tid);
当线程运行结束后,最后显示的调用被回收。这样就出现两种回收方式。
pthread_join
是一个阻塞函数,调用方会阻塞到pthread_join
所指定的tid的线程结束后才被回收,但是在此之前,调用方是霸占系统资源的。pthread_detach
,不会阻塞,调用它后,线程运行结束后会自动释放资源。- 可分离的状态属性可以在
pthread_create
时指定(线程属性),或在线程创建后在线程中pthread_detach
自己, 如:pthread_detach(pthread_self()),将状态改为可分离的状态状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join.
其实简单的说就是在线程函数头加上 pthread_detach(pthread_self())的话,线程状态改变,在函数尾部直接 pthread_exit线程就会自动退出。省去了给线程擦屁股的麻烦
PS:一个可结合线程在运行结束后,若没有调用 pthread_join
,会进入一个类似zombie process
的状态,也就是系统中还有一些资源没有回收。需要pthread_join
来回收这些资源。(这就类似进程操作中的waitpid函数)线程在创建时默认的状态是 joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process(僵尸进程),即还有一部分资源没有被回收(退出状态码),所以创建线程者应该 pthread_join
来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid),这样不会导致系统越用越慢的现象。
但是 pthread_join(pthread_id)
函数是阻塞函数,在调用pthread_join(pthread_id)
后,如果该线程 没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,比如在Web服务器中当 主线程 为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用 pthread_join
而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码
pthread_detach( pthread_self());
或者父线程调用
pthread_detach(thread_id);(非阻塞,可立即返回)
这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
在嵌入式系统中,如果某些线程要伴随系统一直运行下去,该种情况下是否采用该该函数进行回收,没有什么价值。
3. 代码实例
/*
* pthread_join()阻塞回收资源
*/
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void *thread1_func(void *arg)
{
int n;
for (n = 0; n < 30; n++) {
sleep(1);
printf("AAAAAAAAAA\n");
}
pthread_exit(NULL);
//return NULL;
}
void *thread2_func(void *arg)
{
int n;
for (n = 0; n < 30; n++) {
sleep(1);
printf("BBBBBBBBBB\n");
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, thread1_func, NULL) != 0) {
perror("main: pthread_create thread_1 failed");
return 1;
} else {
printf("main: pthread_create thread_1 succeed!\n");
}
if (pthread_create(&tid2, NULL, thread2_func, NULL) != 0) {
perror("main: pthread_create thread_2 failed");
return 1;
} else {
printf("main: pthread_create thread_2 succeed!\n");
}
if (pthread_join(tid1, NULL) != 0) {
perror("main: pthread_join thread_1 failed");
}
if (pthread_join(tid2, NULL) != 0) {
perror("main: pthread_join thread_2 failed");
}
printf("main is exiting.\n");
return 0;
}
以下为一个多线程服务模型
/*
* pthread_detach() 自动释放资源资源
*/
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/**
* @brief 接收客户端发来的消息
* 若客户端发来正常的消息便广播给其他应用,如客户端退出则删除对应客户端节点
* @param ags
* @return void*
*/
void *routine(void *ags)
{
//获取自己的TID, 进而分离自己;将来退出时立即释放资源
pthread_detach(pthread_self());
int connfd = (int)(*((int*)ags));
char buf[SIZE];
while (1)
{
bzero(buf, SIZE);
//1. 关闭客户端或退出
if (Read(connfd, buf, SIZE) == 0 || //read读取的是一个管道,如果对端关闭连接返回0
!strcmp(buf, "quit\n"))
{
del_client(connfd);
break;
}
//2. 转发客户端的信息给到系统的其他客户端节点
broad_cast(buf, connfd);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if (argc != 2) //外部动态输入端口号
{
printf("Usage: %s <PORT>\n", argv[0]);
exit(0);
}
//TCP 创建套接字
int sockfd = Socket(AF_INET, SOCK_STREAM, 0); //AF_INET:IPV4X协议 SOCK_STREAM:流式套接字
struct sockaddr_in seraddr, cliaddr;
socklen_t len = sizeof(seraddr);
bzero(&seraddr, len);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(atoi(argv[1]));
/*自动获取网卡地址*/
seraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//绑定地址
Bind(sockfd, (struct sockaddr *)&seraddr, len);
//设置监听套接字
Listen(sockfd, 3); //在linux中同时发起连接请求个数为3+4=7个
while (1)
{
//持续等待对方连接,设置连接套接字,连接未建立完成处于阻塞中
len = sizeof(cliaddr);
int connfd = Accept(sockfd, (struct sockaddr *)&cliaddr, &len); //此处如需要保存客户端点地址信息,用 cliaddr接收,若不需要则为NULL
char peeraddr[50];
bzero(peeraddr, 50);
printf("new connection: %s:%hu\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, peeraddr, 50),
ntohs(cliaddr.sin_port)); // 打印客户端的IP地址和端口
pthread_t tid;
pthread_create(&tid, NULL, routine, (void *)&connfd);
}
Close(sockfd);
return 0;
}