简单做点Linux多线程编程的记录,包括线程创建和结束,线程同步方法(互斥量、信号量、条件变量)。太理论的东西就不记录了,直接看书好一些。
线程创建与结束
Linux系统上线程创建和结束的API都定义在头文件pthread.h中。
(1)pthread_create
用该函数可以创建一个线程,成功时返回0,失败时返回错误码。
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:start_routine函数的参数。
注:线程相关代码在编译时需要加上-lpthread选项声明需要连接线程库。
(2)pthread_exit
在线程结束时最好调用如下函数以确保安全、干净地退出:
void pthread_exit(void *retval)
该函数通过retval向线程的回收者传递其退出信息。
(3)pthread_join
pthread_join等待线程终止并回收线程,成功时返回0,失败时返回错误码。
int pthread_join(pthread_t thread, void ** retval);
thread:要销毁的线程ID;
retval:线程运行的函数返回值的指针变量地址值。
注意调用该函数时会一直阻塞,直到参数thread所指示的线程结束,再引导线程销毁。
(4)pthread_detach
由于调用pthread_join函数时,调用该函数的线程将处于阻塞状态,因此可换用pthread_detach函数引导线程销毁。成功时返回0,失败时返回错误码。
int pthread_detach(pthread_t thread);
thread也是要销毁的线程ID。调用该函数不会引起线程终止或进入阻塞状态。
线程同步
函数内可能存在临界区,即函数内同时运行多个线程时会引起问题的多条语句构成的代码块。这是由于线程间共享数据区和堆的特性造成的,如某个线程在使用某个变量时,该变量的值被另一个线程修改了,这样就会产生问题。注意临界区并非该变量,而是访问变量的语句。显然这样的语句不能由两或多个线程同时访问。另外,两条不同语句由不同线程同时执行时,也有可能构成临界区(例如语句访问同一个变量)。对于这类问题的解决方法就是“线程同步”,下面是三种专门用于线程同步的机制。
注:其实大致意思就是我这个线程在使用数据的时候,我希望它是前后一致的,如果使用的数据是私有的,或者说是只读的,则显然不会有什么问题。但线程之间共享存储,当一个线程在对数据进行读取的时候另一个线程可能也在对这个数据进行修改,这样线程读取数据就会出现前后不一致的情况。为了保证一致性,我们就要对线程进行同步。
“度娘”解释同步一词的含义是“同步就是协同步调,按预定的先后次序进行运行”,我觉得这样理解有点道理。
互斥量
互斥量主要用于解决线程同步访问的问题。为了保护某个临界区,使其不允许多个线程同时访问,对其进行“上锁”。相关函数定义在头文件pthread.h中。下列函数成功时返回0,失败则返回错误码。
互斥量的初始化及销毁函数:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:创建互斥量时传递保存互斥量的变量地址值,销毁时传递的需要销毁的互斥量地址值。
attr传递即将创建的互斥量属性,没有特别需要指定是属性时传递NULL。
该变量的地址将传递给pthread_mutex_init函数,用来保存操作系统创建的互斥量(锁系统)。调用pthread_mutex_destroy函数的同时需要该信息。
下面是互斥量锁住和释放临界区时使用的函数。
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
这两个函数将临界区锁住,阻止多个线程同时访问临界区。
pthread_mutex_lock(&mutex);
/*
临界区
*/
pthread_mutex_unlock(&mutex);
进入临界区前调用pthread_mutex_lock,如果发现已有其他线程进入临界区,该函数不会返回,当前线程处于阻塞状态,直到里面的线程调用pthread_mutex_unlock退出临界区。
注意,如果线程在退出临界区时未调用pthread_mutex_unlock,则其他其他为了进入临界区而调用pthread_mutex_lock的线程将一直处于阻塞状态。该情况被称为死锁。
信号量
信号量用于控制线程的执行顺序。注意相关函数定义在头文件semaphore.h中,下列函数成功时返回0,失败则返回错误码。
信号量的初始化及销毁函数:
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destory(sem_t *sem);
sem:创建信号量时传递保存信号量的变量地址值,销毁信号量时传递需要销毁的信号量的变量地址值。
pshared:传递其他值时,创建可由多个进程共享的信号量;传递0时,创建只允许1个进程内部使用的信号量,要完成同一进程内的线程同步,传递0。
value:指定新创建的信号量的初值。
如同互斥量的“上锁”,用下面两个函数围住临界区。
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
sem_wait(&sem);
/*
临界区
*/
sem_post(&sem);
sem传递保存信号量读取值的变量地址值,传递给sem_post时信号量增1,传递给sem_wait时信号量减1。
调用sem_init函数,操作系统创建信号量对象,信号量值由value指定。调用sem_post是信号量值增1,调用sem_wait时减1。由于信号量的值不能小于0,因此在信号量值为0时调用sem_wait,线程会阻塞。直到其他线程调用sem_post使信号量增1,阻塞的线程才能使用sem_wait将信号量减1,并跳出阻塞。通过这种特性可以控制线程执行的顺序,实现临界区的同步操作。
条件变量
条件变量用于在线程之间同步共享数据的值。当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。条件变量需要与互斥量一起使用,互斥量用于将条件变量“锁住”,条件变量则用于等待条件。条件变量的相关函数也定义在头文件pthread.h中。下列函数成功时返回0,失败则返回错误码。
条件变量的初始化及销毁函数:
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destroy(pthread_cond_t* cond);
cond:指向要操作的目标条件变量,条件变量的类型是pthread_cond_t结构体。
cond_attr:指定条件变量的属性,设为NULL意为默认属性。
使用pthread_cond_wait函数等待条件变量:
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
mutex为传递给该函数的互斥量,其条件进行保护。调用该函数前将互斥量“加锁”,后将加锁的互斥量传递给该函数,函数自动将调用线程放到等待条件的线程列表上,然后将互斥量“解锁”。该函数成功返回时,互斥量将再次被锁住。
以下两个函数用于通知线程条件已满足:
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
pthread_cond_signal函数(至少)能唤醒一个等待目标条件变量的线程;pthread_cond_broadcast函数唤醒所有等待该条件的线程。
注:就简单记这么多了,具体的使用方法也还是看书好一些,如果还有想要记的以后再补充吧。