Linux中线程的创建与应用
Linux中多线程编程技术被广泛使用,多线程可以提升程序的运行效率和便利性。多线程编程技术普遍存在应用于较大一点的linux程序中。线程间的通信问题就是线程A怎么把消息传递给线程B。目前线程间通信的用的比较多的主要技术有消息队列、共享内存。在实现线程机制中分为Solaris(较早出现) 和POSIX两种,linux中多线程编程的实现POSIX规范,及利用消息队列进行线程间通信。Linux平台线程函数需要包含头文件:#include<pthread.h> 而Solaris使用的头文件是<thread.h>
线程按照其调度者可以分为用户级线程和核心级线程两种。
(1)用户级线程
用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持。在这里,操作系统往往会提供一个用户空间的线程库,该线程库提供了线程的创建、调度、撤销等功能,而内核仍然仅对进程进行管理。如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程中的其他所有线程也同时被阻塞。这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无法发挥多处理器的优势。
(2)核心级线程
这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。
现在大多数系统都采用用户级线程与核心级线程并存的方法。一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。这样既可满足多处理机系统的需要,也可以最大限度地减少调度开销。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。有关Linux平台中线程编程技术可参考Oracle网站专题讲解。题目是:Multithreaded Progamming Guide 链接地址是:
https://docs.oracle.com/cd/E19455-01/806-5257/index.html
条件变量(condition variablex) 使线程阻塞直到变量状态改变。
互斥量(mutual exclusion locks) 对共享数据的互斥锁。
下面是POSIX的线程库。
pthread_create();//使用默认的属性创建一个线程
pthread_arrt_init();//创建一个属性对象控制线程的属性。
pthread_arrt_t tarrt;
pthread_join();//等待一个线程结束并回收线程资源
pthread_detach();//当线程结束时,加收其资源
单线程的C程序有两种数据:局部数据和全局数据,多线程C程序增加了一个:线程专属数据(TSD)。
pthread_key_create();//创建一个key,用于标识线程专属数据。创建无须同步,但是存取须同步控制。
pthread_key_t key;
pthread_key_delete();//销毁一个key,程序员应负责在销毁前释放key关联的资源。
pthread_setspecific();//对key进行绑定。
pthread_getspecific();//获取key绑定的值。
pthread_self();//获取线程标识
sched_yield();//同级或更高级别的线程使当前线程退出。
pthread_setschedparam();//设置当前线程的优先级。
pthread_getschedparam();//获取当前线程的优先级。
pthread_kill();//向线程发送信号 线程必须在同一进程内。
int pthread_kill(thread_t tid, int sig);
#include<pthread.h>
#include<signal.h>
int sig;
pthread_t tid;
int ret;
ret =pthread_kill(tid, sig);
pthread_exit();//使当前线程终止
pthread_cancel();//取消一个线程的执行。
pthread_setcancelstate();//使一个线程可取消或不可取消。
pthread_testcancel();//创建一个取消点。
pthread_arrt_init();//设置属性变量值,用于控制线程。
Creating a Detached Thread //创建一个分享属生的线程
#include <pthread.h>
pthread_attr_t tattr;
pthread_t tid;
void *start_routine;
void arg
int ret;
/* initialized with default attributes */
ret = pthread_attr_init()(&tattr);
ret = pthread_attr_setdetachstate()(&tattr,PTHREAD_CREATE_DETACHED);
ret = pthread_create()(&tid, &tattr, start_routine, arg);
pthread_attr_setstatcksize();//设置线程堆栈大小。
在Linux平台中线程操作相关函数有:
1.线程创建函数pthread_create
2.等待另一线程结束函数pthread_join
3.线程分离函数pthread_detach
4.线程自我结束函数pthread_exit
多线程程序在运行时还需要执行必要的互斥,线程间也可使用信号量进行通讯。
使用pthread_create创建线程
Linux中创建线程使用pthead_create函数,
#inlcude<pthread.h>
int pthread_create();
/*《TCP/IP网络编程》page287
* thread1.c
* 057.c 线程实例1
*/
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void * thread_main(void *arg);
int main(int argc,char *argv[])
{
pthread_t t_id;
int thread_param=5;
if(pthread_create(&t_id,NULL,thread_main,(void*)&thread_param)!=0)
{
puts("pthread_create() error");
return -1;
}
sleep(10);
puts("end of main");
return 0;
}
void *thread_main(void *arg)
{
int i;
int cnt=*((int*)arg);
for(i=0;i<cnt;i++)
{
sleep(1);
puts("running thread");
}
return NULL;
}
" undefined reference to ‘pthread_create’",所有关于线程的函数都会有此错误,导致无法编译通过。问题的原因:pthread不是Linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败。
解决方法:在gcc编译的时候,附加要加 -lpthread参数即可解决。
gcc -o pt pthread.c -lpthread
使用pthread_join等待线程结束
当创建的线程执行完成后资源如何回收呢?默认情况下创建的线程与原有线程属非分离状态,当原有线程调用pthread_join()函数来等待创建的线程结束,创建的线程代码执行完毕释放占用的资源。也可以设置被创建的线程为分离的,这样新创建的线程运行结束后系统马上释放其资源。下面看看pthread_join方法的使用。
/*《Linux网络编程》page127
* pthread.c
* 058.c 线程实例2
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
static int run = 1; /*运行状态参数*/
static int retvalue ; /*线程返回值*/
void *start_routine(void *arg) /*线程处理函数*/
{
int *running = arg; /*获取运行状态指针*/
printf("子线程初始化完毕,传入参数为:%d\n",*running); /*打印信息*/
while(*running) /*当running控制参数有效*/
{
printf("子线程正在运行\n"); /*打印运行信息*/
usleep(1); /*等待*/
}
printf("子线程退出\n"); /*打印退出信息*/
retvalue = 8; /*设置退出值*/
pthread_exit( (void*)&retvalue); /*线程退出并设置退出值*/
}
int main(void)
{
pthread_t pt;
int ret = -1;
int times = 3;
int i = 0;
int *ret_join = NULL;
ret = pthread_create(&pt, NULL, (void*)start_routine, &run); /*建立线程*/
if(ret != 0) /*建立线程失败*/
{
printf("建立线程失败\n"); /*打印信息*/
return 1; /*返回*/
}
usleep(1); /*等待*/
for(;i<times;i++) /*进行3次打印*/
{
printf("主线程打印\n"); /*打印信息*/
usleep(1); /*等待*/
}
run = 0; /*设置线程退出控制值,让线程退出*/
pthread_join(pt,(void*)&ret_join); /*等待线程退出*/
printf("线程返回值为:%d\n",*ret_join); /*打印线程的退出值*/
return 0;
}
线程中使用信号量
编译具有信号量函数的代码也需要使用-lpthread参数
// 059.c 线程间信号量的使用 《TCP/IP网络编程》 page305
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
void * read(void *arg);
void * accu(void *arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;
int main(int argc,char *argv[])
{
pthread_t id_t1,id_t2;
sem_init(&sem_one,0,0);
sem_init(&sem_two,0,1);
pthread_create(&id_t1,NULL,read,NULL);
pthread_create(&id_t2,NULL,accu,NULL);
pthread_join(id_t1,NULL);
pthread_join(id_t2,NULL);
sem_destroy(&sem_one);
sem_destroy(&sem_two);
return 0;
}
void *read(void * arg)
{
int i;
for(i=0;i<5;i++)
{
fputs("Input num: ",stdout);
sem_wait(&sem_two);
scanf("%d",&num);
sem_post(&sem_one);
}
return NULL;
}
void *accu(void * arg)
{
int sum=0,i;
for(i=0;i<5;i++)
{
sem_wait(&sem_one);
sum+=num;
sem_post(&sem_two);
}
printf("Result: %d \n",sum);
return NULL;
}
使用线程的网络程序
Linux平台还支持通过创建线程执行对每个客户端的socket通讯任务。启动ubuntu64系统,他实际安装的是ubuntu12
直接使用gcc命令会出现编译不成功信息。
聊天客户端线程分离发送和接收
// 060.c 使用线程的聊天客户端《TCP/IP网络编程》page310 chat_clnt.c
//聊天程序客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define BUF_SIZE 100
#define NAME_SIZE 20
void* recv_msg(void *arg);
void* send_msg(void *arg);
void error_handling(char *msg);
//全局变量
char name[NAME_SIZE]="[default]";
char msg[BUF_SIZE];
int main(int argc,char *argv[])
{
int sock;
struct sockaddr_in serv_adr;
pthread_t snd_thread,rcv_thread;
void * thread_return;
if(argc!=4)
{
printf("Usage: %s <IP> <port> <name>\n",argv[0]);
exit(1);
}
sprintf(name,"[%s]",argv[3]);
//用于连接和通讯的socket资源
sock=socket(PF_INET,SOCK_STREAM,0);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
//连接目标主机
if(connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1)
error_handling("connect() error");
//创建一个线程管理发送数据
pthread_create(&snd_thread,NULL,send_msg,(void *)&sock);
//创建一个线程管理接收数据
pthread_create(&rcv_thread,NULL,recv_msg,(void *)&sock);
//等待发送线程结束
pthread_join(snd_thread,&thread_return);
//等待接收线程结束
pthread_join(rcv_thread,&thread_return);
//关闭socket文件资源
close(sock);
return 0;
}
//发送线程代码
void * send_msg(void * arg)
{
int sock=*((int *)arg);
char name_msg[NAME_SIZE+BUF_SIZE];
while(1)
{
fgets(msg,BUF_SIZE,stdin);
if(!strcmp(msg,"q\n")||!strcmp(msg,"Q\n"))
{
close(sock);
exit(0);
}
sprintf(name_msg,"%s %s",name,msg);
write(sock,name_msg,strlen(name_msg));
}
return NULL;
}
//接收信息线程代码
void *recv_msg(void *arg)
{
int sock=*((int *)arg);
char name_msg[NAME_SIZE+BUF_SIZE];
int str_len;
while(1)
{
str_len=read(sock,name_msg,NAME_SIZE+BUF_SIZE-1);
if(str_len==-1)
return (void*)-1;
name_msg[str_len]=0;
fputs(name_msg,stdout);
}
return NULL;
}
//输出出错信息后结束本进程
void error_handling(char * message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
多线程并发服务器端实现
下面程序的服务端针对每个客户连接请求,服务端都创建一个新线程进行通讯,更具体点描述是用同一个线程代码生成多个线程体,所以每个通讯的逻辑是完全一样的,但每个线程体使用的资源不一样。
// 061.c 使用线程的并发服务端《TCP/IP网络编程》page307 chat_server.c
//本程序执行socket监听,信息发送操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#define BUF_SIZE 100
#define MAX_CLNT 256
void * handle_clnt(void *arg);
void send_msg(char *msg,int len);
void error_handling(char *msg);
//全局变量
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;
int main(int argc,char *argv[])
{
int serv_sock,clnt_sock;
struct sockaddr_in serv_adr,clnt_adr;
int clnt_adr_sz;
pthread_t t_id;
if(argc!=2)
{
printf("Usage: %s <port>\n",argv[0]);
exit(1);
}
pthread_mutex_init(&mutx,NULL);
//用于监听的socket资源
serv_sock=socket(PF_INET,SOCK_STREAM,0);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
//绑定监听端口
if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1)
error_handling("bind() error");
//执行监听
if(listen(serv_sock,5)==-1)
error_handling("listen() error");
while(1)
{
clnt_adr_sz=sizeof(clnt_adr);
//每个客户连接请求,服务端都创建一个新socket资源用于通讯
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
pthread_mutex_lock(&mutx);
clnt_socks[clnt_cnt++]=clnt_sock;
pthread_mutex_unlock(&mutx);
//创建一个线程管理与客户端的socket通讯
pthread_create(&t_id,NULL,handle_clnt,(void *)&clnt_sock);
pthread_detach(t_id);
printf("Connected client IP: %s \n",inet_ntoa(clnt_adr.sin_addr));
}
//关闭监听socket文件资源
close(serv_sock);
return 0;
}
//线程代码
void * handle_clnt(void * arg)
{
int clnt_sock=*((int *)arg);
int str_len=0,i;
char msg[BUF_SIZE];
while((str_len=read(clnt_sock,msg,sizeof(msg)))!=0)
send_msg(msg,str_len);
pthread_mutex_lock(&mutx);
for(i=0;i<clnt_cnt;i++)//remove disconnected client
{
if(clnt_sock==clnt_socks[i])
{
while(i++<clnt_cnt-1)//向前移动数据元素
clnt_socks[i]=clnt_socks[i+1];
break;
}
}
clnt_cnt--;
pthread_mutex_unlock(&mutx);
close(clnt_sock);
return NULL;
}
void send_msg(char * msg,int len)
{
int i;
pthread_mutex_lock(&mutx);
for(i=0;i<clnt_cnt;i++)
write(clnt_socks[i],msg,len);
pthread_mutex_unlock(&mutx);
}
//输出出错信息后结束本进程
void error_handling(char * message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
该服务程序最大的问题是没有设置退出响应,里面是个死循环。