> 文章列表 > 【linux服务器编程学习】10.多线程编程

【linux服务器编程学习】10.多线程编程

【linux服务器编程学习】10.多线程编程

linux中的线程

线程是linux中完成一个独立任务的完整执行序列,即一个可调度的实体。根据运行环境,可分为内核线程和用户线程,分别由内核和程序线程库调度。关于linux多线程编程,需要掌握怎么创建和结束线程,怎么读取和设置线程属性,线程同步方式等。

创建和结束线程

  • 线程的创建
#include<pthread.h>
int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void *(start_routline)(void*),void* arg);
thread 是新线程的标识符。
attr用于设置新线程的属性。
start_routline 设置线程将运行的函数
arg是线程运行函数的参数。
成功返回0,失败返回错误码
  • 线程的结束
    pthread_exit 函数通过retval参数向线程的回收者传递其退出信息。
#include<pthread.h>
void pthread_exit(void* retval);

pthread_join 用于回收线程。

#include<pthread.h>
int pthread_join(pthread_t thread,void ** retval);
thread是目标线程的标识符,retval是目标线程返回的退出信息。
成功返回0,失败返回错误码。
该函数会一直阻塞,直到被回收的线程结束。

pthread_cancel立即取消线程。

#include<pthread.h>
int pthread_cancel(pthread_t thread);
thread是线程标识符
成功返回0,失败返回错误码

接收到取消请求的目标线程可以决定是否允许被取消以及如何取消。

#include<pthread.h>
int pthread_setcancelstate(int state,int *oldstate);
int pthread_setcanceltype(int type,int *oldtype);
第一个参数用于设置线程的取消状态(是否允许取消)和取消类型(如何取消)
第二个参数用于记录原来的取消状态和取消类型。state取值:
PTHREAD_CANCEL_ENABLE  允许线程被取消默认状态
PTHREAD_CANCEL_DISABLE  禁止线程被取消,收到请求取消会挂起,知道允许取消
type取值:
PTHREAD_CANCEL_ASYNCHRONOUS:随时可以被取消
PTHREAD_CANCEL_DEFERRED:允许目标线程推迟行动

POSIX信号量

linux的信号量API有两组,在进程间通信的是System V IPC信号量,用于线程间同步的是POSIX信号量。这两组信号量接口类似,但是不保证能互换。

#include<semphore.h>
int sem_init(sem_t* sem,int pshared,unsigned int value);
初始化一个未命名的信号量,pshared 指定信号量类型,为0表示只能在当前进程中使用,否则在多进程共享,value表示信号量初始值
int semdestroy(sem_t sem);
销毁信号量
int sem_wait(sem_t* sem);
信号量值减1,如变为0则阻塞。
int sem_trywait(sem_t* sem)
非阻塞的sem_wait,如-1后为0,则立即返回-1.
int sem_post(sem_t * sem);
信号量值加1,如阻塞就唤醒

互斥锁

互斥锁可以保护临界区代码段,确保独占式访问。当进入临界区,需要获得互斥锁并加锁,当离开临界区,需要对互斥锁解锁,唤醒其他等待该互斥锁的线程。

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
初始化互斥锁,mutexattr通常设为NULLint pthread_mutex_destroy(pthread_mutex_t* mutex);
销毁互斥锁int pthread_mutex_lock(pthread_mutex_t* mutex);
互斥锁加锁int pthread_mutex_trylock(pthread_mutex_t* mutex);
互斥锁非阻塞加锁int pthread_mutex_unlock(pthread_mutex_t* mutex);
互斥锁解锁成功返回0,失败返回错误码

条件变量

如果说互斥锁用于同步线程对共享数据的访问,条件变量机制用于在线程之间同步共享数据的值。
条件变量提供一种线程间通知机制,当某个共享数据到达某个值时,唤醒等待这个共享数据的值。

#include<pthread.h>
int pthread_cond_init(pthread_cond_t* cond,const pthread_condattr_t* cond_attr)
初始化条件变量,cond_attr参数指定条件变量的属性,通常设为NULL
int pthread_cond_destroy(pthread_cond_t* cond);
销毁一个条件变量,销毁一个正在等待的条件变量将返回EBUSY
int pthread_cond_broadcast(pthread_cond_t* cond);
以广播的方式,唤醒所有等待目标条件变量的线程
int pthread_cond_signal(pthread_cond_t* cond);
唤醒一个等待目标条件变量的线程
int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
用于等待目标条件变量,mutex参数用于保护条件变量,需要确保存在且上锁。成功返回0,否则返回错误码。

可重入函数

如果一个函数能被多个线程同时调用且不发生竞态条件,成为线程安全的,或者说是可重入函数。linux库函数只有一小部分是不可重入的。很多不可重入函数被提供了可重入版本,在原函数名尾部加_r。

线程和进程

如果一个多线程程序的某个线程调用了fork函数,新创建的子进程不会自动创建和父进程同样数量的线程。子进程只有一个执行线程,是调用fork那个线程的完整复制,并且子进程会自动继承父进程中互斥锁、条件变量的状态,但是不知道是否上锁,如果这时再加锁就会导致死锁。

pthread提供了一个专门的函数pthread_atfork,确保fork调用后父进程和子进程都有一个清楚的状态。

线程和信号

每个线程都可以独立的设置信号掩码,但是多线程环境下应该使用pthread_sigmask设置信号掩码,与sigprocmask参数和返回值完全相同。

由于进程中的所有线程共享进程的信号和信号处理函数。线程库将根据线程掩码决定把信号发送给哪个具体线程,如果在每个线程中单独设置信号掩码容易出错。

应该定义一个专门的线程处理信号。首先在主线程创建子线程之前就设置好信号掩码,让子线程继承这个信号掩码,然后再某个线程中调用sigwait等待信号并处理。

此外还可以使用pthread_kill 明确的将一个信号发送给指定线程。

后记

机遇固然是好的,但是我们不能只依赖机遇,即便它来了,你也无法保证能抓住它。不如保持长期的计划,做好充足的准备,最后自己创造机遇。