一:POSIX线程的优点
POSIX(可移植操作系统接口)线程是提高代码响应和性能的有力手段,体现在如下几点:
1)线程拥有并发处理能力
线程类似于进程。如同进程,线程由内核按时间分片进行管理。在单处理器系统中,内核使用时间分片来模拟线程的并发执行,这种方式和进程的相同。而在多处理器系统中,如同多个进程,线程实际上一样可以并发执行。
2)线程间的共享内存机制
对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序中的所有线程都可以读或写声明过的全局变量。如果用fork()创建多个进程,它会带来以下通信问题:如何让多个进程相互通信,这里每个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不同种类的本地 IPC (进程间通信),但它们都遇到两个重要障碍:a.强加了某种形式的额外内核开销,从而降低性能;b.对于大多数情形,IPC 不是对于代码的“自然”扩展。通常极大地增加了程序的复杂性。
3)线程的开销小
与标准fork()相比,线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的CPU时间,使得线程创建比新进程创建快上十到一百倍。因为这一点,可以大量使用线程而无需太过于担心带来的 CPU或内存不足。使用 fork() 时导致的大量 CPU 占用也不复存在。这表示只要在程序中有意义,通常就可以创建线程。
4)线程的可移植性
__clone()类似于fork(),同时也有许多线程的特性。__clone调用是特定于Linux平台的,不适用于实现可移植的程序。如果想编写、可移植的、多线程代码,代码可运行于 Solaris、FreeBSD、Linux 和其它平台,POSIX 线程是一种当然之选。
二:POSIX线程函数
POSIX线程库函数绝大多数以“pthread_”打头的;要使用这些函数库,要引入头文件pthread.h;链接这些线程函数库时,要使用编译器命令的"-lpthread"的选项。
1)创建线程函数和线程属性函数
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
@pthread_create:创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数;
@int:pthread_create的返回值,表示成功,返回0;表示出错,返回-1;
@thread:为指向线程标识符的指针;
@attr:用来设置线程属性,默认为NULL;
@void *:是线程运行函数的地址;
@arg:是运行函数的参数;
int pthread_attr_init(pthread_attr_t *attr);
@pthread_attr_init:初始化属性
@int:表示成功,返回0;表示出错,返回-1;
int pthread_attr_destory(pthread_attr_t *attr);
@pthread_attr_destory:销毁属性
@int:表示成功,返回0;表示出错,返回-1;
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
@pthread_attr_getdetachstate:获取分离属性;
int pthread_attr_setdetachstate(const pthread_attr_t *attr, int *detachstate);
@pthread_attr_setdetachstate:设置分离属性;
int pthread_attr_setguardsize(const pthread_attr_t *attr, size_t guardsize);
@pthread_attr_setguardsize:设置栈溢出保护区大小
int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t guardsize);
@pthread_attr_getguardsize:获取栈溢出保护区大小
int pthread_attr_getscope(const pthread_attr_t *attr, int* contentionscope);
@pthread_attr_getscope:获取线程竞争范围
int pthread_attr_setscope(pthread_attr_t *attr, int* contentionscope);
@pthread_attr_setscope:设置线程竞争范围
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int* policy);
@pthread_attr_getschedpolicy:获取线程调度策略
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int* policy);
@pthread_attr_setschedpolicy:设置线程调度策略
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int* policy);
@pthread_attr_getinheritsched:获取继承调度策略
int pthread_attr_setsinheritsched(pthread_attr_t *attr, int* policy);
@pthread_attr_setsinheritsched:设置继承调度策略
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param* param);
@pthread_attr_getschedparam:获取调度参数
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param* param);
@pthread_attr_setschedparam:设置调度参数
2)线程合并和分离
#include
int pthread_join(pthread_t thread, void **retval);
@pthread_join:子线程和主线程合并,阻塞等待线程结束并回收资源函数;
int pthread_detach(pthread_t thread);
@pthread_detach:子线程和主线程分离,主线程不会等子线程结束再结束;
3)线程退出函数
#include
void pthread_exit(void *retval);
@pthread_exit:终止调用它的线程并返回一个指向某个对象的指针;
@retval:pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr
int pthread_cancel(pthread_t thread);
@pthread_cancel:由其他线程来结束线程运行,而pthread_exit是自杀行为;
@int:函数成功完成之后会返回零,其他任何返回值都表示出现了错误;
4)线程互斥锁
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
@pthread_mutex_init:以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性;
@int:函数成功完成之后会返回零,其他任何返回值都表示出现了错误;
有两种定义互斥锁方式:
a.静态定义:pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
b.动态定义:先定义变量:pthread_mutex_t mutex;,然后调用int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 函数进行初始化,通常,互斥锁属性mutexattr为NULL,表示使用默认的属性,即:快速互斥锁。
int pthread_mutex_destroy(pthread_mutex_t *mutex)
@pthread_mutex_destroy:销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态;
int pthread_mutex_lock(pthread_mutex_t *mutex)
@pthread_mutex_lock:如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁.
@int:返回值: 成功则返回0, 出错则返回错误编号。
int pthread_mutex_trylock(pthread_mutex_t *mutex)
@pthread_mutex_trylock:非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态;
@int:返回值: 成功则返回0, 出错则返回错误编号。
int pthread_mutex_unlock(pthread_mutex_t *mutex)
@pthread_mutex_unlock:解锁;
@int:返回值: 成功则返回0, 出错则返回错误编号。
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
@pthread_attr_setstacksize:设置栈大小
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t stacksize);
@pthread_attr_getstacksize:获取栈大小
5)线程信号量
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
@sem_init:sem_init函数是Posix信号量操作中的函数。sem_init() 初始化一个定位在 sem 的匿名信号量;
@int:成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值
@sem :指向信号量对象
@pshared : 指明信号量的类型。不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。
@value : 指定信号量值的大小
int sem_wait(sem_t *sem);
@sem_wait:sem_wait是一个函数,也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,将信号量的值将减到1
@int:成功返回0,错误的话信号量的值不改动,返回-1.errno设定来标识错误
int sem_trywait(sem_t *sem);
@sem_trywait:函数sem_trywait()和sem_wait()有一点不同,即如果信号量的当前值为0,则返回错误而不是阻塞调用。错误值errno设置为EAGAIN。sem_trywait()其实是sem_wait()的非阻塞版本。
@int:成功返回0,错误的话信号量的值不改动,返回-1.errno设定来标识错误
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
@sem_timedwait:sem_timewait 函数会阻塞当前线程直到拿到锁或超时才会返回。阻塞的实现方式就是休眠当前线程,直到锁释放或者超时后唤醒。
@int:成功返回0,错误的话信号量的值不改动,返回-1.errno设定来标识错误
int sem_post(sem_t *sem);
@sem_post:sem_post是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同 时对同一个文件进行读和写操作的两个程序就有可能会引起冲突
@int:sem_post() 成功时返回 0;错误时,信号量的值没有更改,-1 被返回,并设置 errno 来指明错误
6)线程条件变量
#include
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
@pthread_cond_init:初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。否则条件变量的属性将由cattr中的属性值来决定。调用pthread_cond_init函数时,参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。这个函数返回时,条件变量被存放在参数cv指向的内存中。
@int:函数成功返回0;任何其他返回值都表示错误
有两种定义条件变量方式:
a.静态定义:pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
b.动态定义:pthread_cond_init()函数
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
@pthread_cond_wait:阻塞等待;
@int:函数成功返回0;任何其他返回值都表示错误
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
@pthread_cond_timedwait:计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEDOUT,结束等待
@int:函数成功返回0;任何其他返回值都表示错误
int pthread_cond_broadcast(pthread_cond_t *cond);
@pthread_cond_broadcast:唤醒所有正在pthread_cond_wait(&cond1,&mutex1)的线程;
int pthread_cond_signal(pthread_cond_t *cond);
@pthread_cond_signal:发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回;
@int:函数成功返回0;任何其他返回值都表示错误
7)线程ID
pthread_t pthread_self(void);
@pthread_self:显示线程ID;
@pthread_t:成功返回线程ID,失败返回-1;
8)线程特定数据
创建或删除特定的数据;
int pthread_key_create(pthread_key_t* key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t* key);
9)设置或者获取特定数据中的值;
void* pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void* value);
10)只调用一次
int pthread_once(pthread_once_t* once_control, void(*)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;
三:死锁
a.出现的环境:发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生
b.避免方法:
对共享资源操作前一定要获得锁;完成操作以后一定要释放锁;尽量短时间地占用锁;如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC;线程错误返回时应该释放它所获得的锁.
注意:pthread_mutex_trylock()与pthread_mutex_lock()类似,但是在锁已经被占据时返回EBUSY而不是挂起等待
四:创建线程和线程属性
线程创建后,默认是PTHREAD_CREATE_JOINABLE状态,即不可分离的,需要调用pthread_join()函数避免僵进程。因此可能出现主线程结束,但子线程还没有执行完。
//编译命令:g++ -o pthread pthreadCreate.cpp -lpthread
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
void* thread_routine( void *ptr )
{
for (int i=0; i<20; i++)
{
printf("B");
fflush(stdout);
}
}
int main(int argc, char** argv)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_routine, NULL) !=0)
ERR_EXIT("pthread_create");
//获取分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
int state;
pthread_attr_getdetachstate(&attr, &state);
if (state == PTHREAD_CREATE_JOINABLE)
printf("detachstate:PTHREAD_CREATE_JOINABLE\n");
else if (state == PTHREAD_CREATE_DETACHED)
printf("detachstate:PTHREAD_CREATE_DETACHED\n");
//获取栈大小
size_t size;
pthread_attr_getstacksize(&attr, &size);
printf("stacksize:%d\n", size);
//获取栈溢出保护区大小
pthread_attr_getguardsize(&attr, &size);
printf("guardsize:%d\n", size);
for (int i=0; i<20; i++)
{
printf("A");
fflush(stdout);
}
return 0;
}
//执行结果,发现没有打印出'B',那是因为主线程已经执行完毕了,程序就退出了,导致子线程没有被执行。
root@ubuntu:/home/share/eclipse_workspace/demo# ./pthread
detachstate:PTHREAD_CREATE_JOINABLE
stacksize:8388608
guardsize:4096
AAAAAAAAAAAAAAAAAAAAroot@ubuntu:/home/share/eclipse_workspace/demo#
五:pthread_join
pthread_join() 将两个线程合并为一个线程。pthread_join() 的第一个参数是 tid mythread。第二个参数是指向 void 指针的指针。如果 void 指针不为 NULL,pthread_join 将线程的 void*返回值放置在指定的位置上。由于我们不必理会 thread_function() 的返回值,所以将其设为 NULL。
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
void* thread_routine( void *ptr )
{
for (int i=0; i<20; i++)
{
printf("B");
fflush(stdout);
}
}
int main(int argc, char** argv)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_routine, NULL) !=0)
ERR_EXIT("pthread_create");
for (int i=0; i<20; i++)
{
printf("A");
fflush(stdout);
}
if (pthread_join(tid, NULL) !=0)
ERR_EXIT("pthread_join");
return 0;
}
//输出结果
root@ubuntu:/home/share/eclipse_workspace/demo# ./pthread
AAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBroot@ubuntu:/home/share/eclipse_workspace/demo#
六:pthread_detach
pthread_detach() 将子线程和主线程分离;
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
void* thread_routine( void *ptr )
{
for (int i=0; i<20; i++)
{
printf("B");
fflush(stdout);
}
}
int main(int argc, char** argv)
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_routine, NULL) !=0)
ERR_EXIT("pthread_create");
if (pthread_detach(tid) !=0)
ERR_EXIT("pthread_detach");
for (int i=0; i<20; i++)
{
printf("A");
fflush(stdout);
}
return 0;
}
//输出结果
root@ubuntu:/home/share/eclipse_workspace/demo# ./pthread
AAAAAAAAAAAAAAAAAAAAroot@ubuntu:/home/share/eclipse_workspace/demo#
七:pthread_exit
pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
void* thread_routine(void* ptr)
{
const char* msgExit = "thread all done";
pthread_exit ((void*)msgExit); // 重点看 pthread_exit() 的参数,是一个字串,这个参数的指针可以通过
}
int main()
{
pthread_t tid;
void *result;
if (pthread_create(&tid, NULL, thread_routine, NULL) !=0)
ERR_EXIT("pthread_create");
if (pthread_join(tid, &result) !=0)
ERR_EXIT("pthread_join");
printf("pthread_join returns: %s\n",(char *)result);
return 0;
}
//结果
root@ubuntu:/home/share/eclipse_workspace/demo# ./pthread
pthread_join returns: thread all done
八:利用线程实现点对点的多客户端回射服务器
有客户端连接进来后,新开一个线程与客户端交互;
//server.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
void* echo_srv(void* ptr)
{
int conn = *((int*)ptr);
char recvbuf[1024];
while (true)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if (ret == 0)
break;
else if (ret == -1)
ERR_EXIT("read");
fputs(recvbuf, stdout);
fflush(stdout);
write(conn, recvbuf, ret);
}
close(conn);
printf("client close, tid=%ld exit\n", pthread_self());
pthread_exit(NULL);
}
int main(int argc, char** argv)
{
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
while (true)
{
int conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
if (conn < 0)
ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pthread_t tid;
if (pthread_create(&tid, NULL, echo_srv, (void*)&conn) !=0)
ERR_EXIT("pthread_create");
}
close(listenfd);
return 0;
}
//client.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
void echo_cli(int sock)
{
char sendbuf[1024] = {0};
char recvbuf[1024] ={0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
write(sock, sendbuf, strlen(sendbuf));
read(sock, recvbuf, sizeof(recvbuf));
fputs(recvbuf, stdout);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect");
echo_cli(sock);
return 0;
}
//结果:
root@ubuntu:/home/share/eclipse_workspace/demo# ./client
dfdfdf
dfdfdf
^C
root@ubuntu:/home/share/eclipse_workspace/demo#
root@ubuntu:/home/share/eclipse_workspace/demo# ./server
ip=127.0.0.1 port=35428
hello
client close, tid=140405590841088 exit
ip=127.0.0.1 port=35430
dfdfdf
client close, tid=140405580261120 exit
^C
root@ubuntu:/home/share/eclipse_workspace/demo#
九:线程特定数据
在多线程中,由于数据空间是共享的,因此全局变量也为所有线程所共有,但是当有必要提供线程私有的全局变量的需求时,应该怎么处理呢。POSIX线程库通过维护一定的数据结构来解决这个问题,这些数据被称为TSD(Thread-specific Data)。
#include
#include
#include
#include
#define ERR_EXIT(m) do{ perror(m); exit(EXIT_FAILURE);}while(0);
pthread_key_t key_tsd;
typedef struct tsd
{
pthread_t tid;
char *str;
}tsd_t;
void* thread_routine(void *argv)
{
tsd_t *value = (tsd_t*)malloc(sizeof(tsd_t));
value->tid = pthread_self();
value->str = (char*)argv;
//设置特定的数据
pthread_setspecific(key_tsd, value);
printf("%s setpecific %p\n", (char*)argv, value);
//取出特定数据里面的值
value = (tsd_t *)pthread_getspecific(key_tsd);
printf("tid=0x%x str=%s\n", (int)value->tid, value->str);
//睡眠2秒,查看是否该线程的特定数据值被其他线程更改;
sleep(2);
value = (tsd_t *)pthread_getspecific(key_tsd);
printf("tid=0x%x str=%s\n", (int)value->tid, value->str);
}
void destroy_routine(void *value)
{
printf("tid=%ld destory...\n", pthread_self());
if (value != NULL)
{
free(value);
value = NULL;
}
}
int main(int argc, char** argv)
{
//线程退出会调用destory_routine函数
pthread_key_create(&key_tsd, destroy_routine);
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, thread_routine, (void*)"thread1") !=0)
ERR_EXIT("pthread_create");
if (pthread_create(&tid2, NULL, thread_routine, (void*)"thread2") !=0)
ERR_EXIT("pthread_create");
if (pthread_join(tid1, NULL) !=0 )
ERR_EXIT("pthread_join");
if (pthread_join(tid2, NULL) !=0 )
ERR_EXIT("pthread_join");
pthread_key_delete(key_tsd);
return 0;
}