多线程
多线程编程
线程是一个进程内的基本调度单位,也可以称为轻量级进程。
线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述和信号处理。因此,大大减少了上下文切换的开销。
Linux线程实现
线程创建和退出
创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是 pthread_create
。
在线程创建以后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种方法。另一种退出线程的方法是使用函数 pthread_exit
。
通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。
pthread_join
可以用于将当前线程挂起,等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
void pthread_exit(void *retval);
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
实例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void thread1(void)
{
int i=0;
for(i=0;i<6;i++){
printf("This is a pthread1.\n");
if(i==2)
pthread_exit(0);
sleep(1);
}
}
void thread2(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread2.\n");
pthread_exit(0);
}
int main(void)
{
pthread_t id1,id2;
int i,ret;
if(pthread_create(&id1,NULL,(void *) thread1,NULL) != 0){
printf ("Create pthread error!\n");
exit (1);
}
if(pthread_create(&id2,NULL,(void *) thread2,NULL) != 0){
printf ("Create pthread error!\n");
exit (1);
}
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit (0);
}
结果如下:
xxx@xxx-pc:~/Documents$ ./a.out
This is a pthread1.
This is a pthread2.
This is a pthread2.
This is a pthread2.
This is a pthread1.
This is a pthread1.
修改线程属性
pthread_create 函数的第二个参数——线程的属性。
设为 NULL,也就是采用默认属性,线程的多项属性都是可以更改的。
这些属性主要包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。
其中系统默认的属性为非绑定、非分离、缺省1M 的堆栈、与父进程同样级别的优先级。
1.绑定属性
Linux 中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。
绑定属性就是指一个用户线程固定地分配给一个内核线程,而与之相对的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。
2.分离属性
分离属性是用来决定一个线程以什么样的方式来终止自己。
在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正的终止。只有当 pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。
而在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。
-
pthread_attr_init 函数进行初始化
-
pthread_attr_setscope 设置绑定属性
-
pthread_attr_setdetachstate 设置线程分离属性
-
pthread_attr_getschedparam 获取线程优先级 和 pthread_attr_setschedparam 设置线程优先级
在设置完这些属性后,就可以调用 pthread_create 函数来创建线程了。
实例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void thread1(void)
{
int i=0;
for(i=0;i<3;i++){
printf("This is a pthread1.\n");
if(i==1)
pthread_exit(0);
sleep(1);
}
}
void thread2(void)
{
int i;
while(1){
printf("This is a pthread2.\n");
sleep(1);
}
pthread_exit(0);
}
int main(void)
{
pthread_t id1,id2;
int i,ret;
pthread_attr_t attr;
/* 初始化线程 */
pthread_attr_init(&attr);
/* 设置线程绑定属性 */
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
/* 设置线程分离属性 */
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(pthread_create(&id1,&attr,(void *) thread1,NULL) != 0){
printf ("Create pthread error!\n");
exit (1);
}
if(pthread_create(&id2,NULL,(void *) thread2,NULL) != 0){
printf ("Create pthread error!\n");
exit (1);
}
pthread_join(id2,NULL);
return (0);
}
结果如下:
xxx@xxx-pc:~/Documents$ ./a.out
This is a pthread1.
This is a pthread2.
This is a pthread1.
This is a pthread2.
This is a pthread2.
线程访问控制
由于线程共享进程的资源和地址空间,因此在对这些资源进行操作时,必须考虑到线程间资源访问的惟一性问题,这里主要介绍 POSIX 中线程同步的方法,主要有互斥锁和信号量的方式。
1.mutex 互斥锁线程控制
mutex 是一种简单的加锁的方法来控制对共享资源的存取。这个互斥锁只有两种状态,也就是上锁和解锁。
在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行操作。
若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
锁共有3种类型:
PTHREAD_MUTEX_INITIALIZER - 快速互斥锁。调用线程会阻塞直至拥有互斥锁的线程解锁为止。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP - 递归互斥锁。能够成功地返回并且增加调用线程在互斥上加锁的次数。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP - 检错互斥锁。它是快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
实例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
void pthread1(void *arg);
void pthread2(void *arg);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int lock_var;
time_t end_time;
int main(int argc, char *argv[])
{
pthread_t id1,id2;
pthread_t mon_th_id;
int ret;
end_time = time(NULL)+10;
/* 互斥锁初始化 */
pthread_mutex_init(&mutex,NULL);
if(pthread_create(&id1,NULL,(void *)pthread1, NULL) != 0)
perror("pthread create 1");
if(pthread_create(&id2,NULL,(void *)pthread2, NULL) != 0)
perror("pthread create2");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void pthread1(void *arg)
{
int i;
while(time(NULL) < end_time){
/* 互斥锁上锁 */
if(pthread_mutex_lock(&mutex) != 0){
perror("pthread_mutex_lock");
}else
printf("pthread1: lock the variable\n");
for(i=0;i<2;i++){
sleep(1);
lock_var++;
}
/* 互斥锁解锁 */
if(pthread_mutex_unlock(&mutex) != 0){
perror("pthread_mutex_unlock");
}else
printf("pthread1: unlock the variable\n");
sleep(1);
}
}
void pthread2(void *arg)
{
int nolock=0;
int ret;
while(time(NULL) < end_time){
/* 测试互斥锁,成功也能上锁 */
ret=pthread_mutex_trylock(&mutex);
if(ret==EBUSY)
printf("pthread2: the variable is locked by pthread1\n");
else{
if(ret!=0){
perror("pthread_mutex_trylock");
exit(1);
}else
printf("pthread2: got lock.The variable is %d\n",lock_var);
/* 互斥锁解锁 */
if(pthread_mutex_unlock(&mutex)!=0){
perror("pthread_mutex_unlock");
}else
printf("pthread2: unlock the variable\n");
}
sleep(3);
}
}
结果如下:
xxx@xxx-pc:~/Documents$ ./a.out
pthread2: got lock.The variable is 0
pthread2: unlock the variable
pthread1: lock the variable
pthread1: unlock the variable
pthread2: got lock.The variable is 2
pthread2: unlock the variable
pthread1: lock the variable
pthread1: unlock the variable
pthread2: got lock.The variable is 4
pthread2: unlock the variable
pthread1: lock the variable
pthread1: unlock the variable
pthread2: got lock.The variable is 6
pthread2: unlock the variable
pthread1: lock the variable
pthread1: unlock the variable
2.信号量 线程控制
信号量也就是操作系统中所用到的 PV 原语,它广泛用于进程或
线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。
PV 原语是对整数计数器信号量 sem 的操作。一次 P 操作使 sem 减一,而一次 V 操作使sem 加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。
当信号量 sem 的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量 sem的值小于零时,该进程(或线程)就将阻塞直到信号量 sem 的值大于等于 0 为止。
SYNOPSIS
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
/* 相当于P操作, 使sem-1, sem小于0则会阻塞 */
int sem_wait(sem_t *sem);
/* 相当于P操作,使sem-1, sem小于0立即返回 */
int sem_trywait(sem_t *sem);
/* 相当于V操作, 使sem+1, 同时发出信号唤醒等待的进程 */
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);
int sem_destroy(sem_t *sem);
Link with -pthread.
PV 原语主要用于进程或线程间的同步和互斥这两种典型情况。
- 互斥
几个进程(或线程)往往只设置一个信号量 sem。
实例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>
void pthread1(void *arg);
void pthread2(void *arg);
int lock_var;
time_t end_time;
sem_t sem;
int main(int argc, char *argv[])
{
pthread_t id1,id2;
int ret;
end_time = time(NULL)+10;
/* 初始化信号量为 1*/
if(sem_init(&sem,0,1) != 0){
perror("sem_init");
}
if(pthread_create(&id1,NULL,(void *)pthread1, NULL) != 0)
perror("pthread cread1");
if(pthread_create(&id2,NULL,(void *)pthread2, NULL) != 0)
perror("pthread cread2");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void pthread1(void *arg)
{
int i;
while(time(NULL) < end_time){
/* 信号量减一,P 操作 */
sem_wait(&sem);
for(i=0;i<2;i++){
sleep(1);
lock_var++;
printf("lock_var = %d\n",lock_var);
}
printf("pthread1:lock_var=%d\n",lock_var);
/*信号量加一,V 操作*/
sem_post(&sem);
sleep(1);
}
}
void pthread2(void *arg)
{
int nolock=0;
int ret;
while(time(NULL) < end_time){
/*信号量减一,P 操作 */
sem_wait(&sem);
printf("pthread2: got lock;lock_var=%d\n",lock_var);
/* 信号量加一,V 操作 */
sem_post(&sem);
sleep(3);
}
}
结果如下:
xxx@xxx-pc:~/Documents$ ./a.out
lock_var = 1
lock_var = 2
pthread1:lock_var=2
pthread2: got lock;lock_var=2
lock_var = 3
lock_var = 4
pthread1:lock_var=4
pthread2: got lock;lock_var=4
lock_var = 5
lock_var = 6
pthread1:lock_var=6
pthread2: got lock;lock_var=6
lock_var = 7
lock_var = 8
pthread1:lock_var=8
- 同步
当信号量用于同步操作时,往往会设置多个信号量,并安排不同的初始值来实现它们之间的顺序执行。
实例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>
void pthread1(void *arg);
void pthread2(void *arg);
time_t end_time;
sem_t sem1,sem2;
int main(int argc, char *argv[])
{
pthread_t id1,id2;
end_time = time(NULL)+10;
/* 初始化两个信号量, sem1为 1, sem2为 0 */
if(sem_init(&sem1,0,1) != 0){
perror("sem_init 1");
}
if(sem_init(&sem2,0,0) != 0){
perror("sem_init 2");
}
if(pthread_create(&id1,NULL,(void *)pthread1, NULL) != 0)
perror("pthread create 1");
if(pthread_create(&id2,NULL,(void *)pthread2, NULL) != 0)
perror("pthread cread2");
sem_destroy(&sem1);
sem_destroy(&sem2);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void pthread1(void *arg)
{
int value1 = 0, value2 = 0;
while(time(NULL) < end_time){
/* P 操作信号量 2 */
sem_wait(&sem2);
sem_getvalue(&sem1, &value1);
sem_getvalue(&sem2, &value2);
printf("pthread1: before locking, sem1 = %d, sem2 = %d\n", value1, value2+1);
sleep(2);
printf("pthread1: on lock, do sleeping 2s\n");
/* V 操作信号量 1 */
sem_post(&sem1);
sleep(1);
}
}
void pthread2(void *arg)
{
int value1 = 0, value2 = 0;
while(time(NULL) < end_time){
/* P 操作信号量 1 */
sem_wait(&sem1);
sem_getvalue(&sem1, &value1);
sem_getvalue(&sem2, &value2);
printf("pthread2: before locking, sem1 = %d, sem2 = %d\n", value1+1, value2);
printf("pthread2: on lock, doing nothing\n");
/* V 操作信号量 2 */
sem_post(&sem2);
sleep(3);
}
}
结果如下:
pthread2先执行,然后它们交替执行。sem1和sem2的值也交替变化。
可以看出,sem == 0 时, 执行 P 操作,sem == -1, 线程此时被阻塞。
xxx@xxx-pc:~/Documents$ ./a.out
pthread2: before locking, sem1 = 1, sem2 = 0
pthread2: on lock, doing nothing
pthread1: before locking, sem1 = 0, sem2 = 1
pthread1: on lock, do sleeping 2s
pthread2: before locking, sem1 = 1, sem2 = 0
pthread2: on lock, doing nothing
pthread1: before locking, sem1 = 0, sem2 = 1
pthread1: on lock, do sleeping 2s
pthread2: before locking, sem1 = 1, sem2 = 0
pthread2: on lock, doing nothing
pthread1: before locking, sem1 = 0, sem2 = 1
pthread1: on lock, do sleeping 2s
pthread2: before locking, sem1 = 1, sem2 = 0
pthread2: on lock, doing nothing
pthread1: before locking, sem1 = 0, sem2 = 1
pthread1: on lock, do sleeping 2s
经典进程同步问题 - 消费者生产者问题
有一个有限缓冲区和两个线程:生产者和消费者。他们分别把产品放入缓冲区和从缓冲区中拿走产品。当一个生产者在缓冲区满时必须等待,当一个消费者在缓冲区空时页必须等
待。
这里有相关的参考内容,写的很好。
这里使用 3 个信号量, 其中两个信号量 avail 和 full 分别用于解决生产者和消费者线程之间的同步问题,mutex 是用于这两个线程之间的互斥问题。其中 avail 初始化为 N(有界缓冲区的空单元数),mutex 初始化为 1,full 初始化为 0。
实例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ipc.h>
#include <semaphore.h>
#include <fcntl.h>
#define FIFO "myfifo"
#define N 5
void productor(void *arg);
void consumer(void *arg);
time_t end_time;
char buf_r[100];
sem_t mutex,full,avail;
int fd;
int main(int argc, char *argv[])
{
pthread_t id1,id2;
int ret = 0;
end_time = time(NULL)+5;
/* 创建有名管道 */
if((mkfifo(FIFO,O_CREAT|O_EXCL|0600) < 0)&&(errno != EEXIST))
printf("cannot create fifoserver\n");
printf("Preparing for reading bytes...\n");
memset(buf_r,0,sizeof(buf_r));
/* 打开管道 */
fd = open(FIFO,O_RDWR|O_NONBLOCK,0);
if(fd == -1){
perror("open");
exit(1);
}
/* 初始化互斥信号量为 1*/
ret = sem_init(&mutex,0,1);
/* 初始化 avail 信号量为 N */
ret = sem_init(&avail,0,N);
/* 初始化 full 信号量为 0 */
ret = sem_init(&full,0,0);
if(ret != 0){
perror("sem_init");
}
/* 创建两个线程 */
if(pthread_create(&id1,NULL,(void *)productor, NULL) != 0)
perror("pthread cread1");
if(pthread_create(&id2,NULL,(void *)consumer, NULL) != 0)
perror("pthread cread2");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
exit(0);
}
void productor(void *arg)
{
int i,nwrite;
while(time(NULL) < end_time){
/* P 操作信号量 avail 和 mutex */
sem_wait(&avail);
sem_wait(&mutex);
/* 生产者写入数据 */
if((nwrite=write(fd,"hello",5)) == -1){
if(errno == EAGAIN)
printf("The FIFO has not been read yet.Please try later\n");
}else
printf("write hello to the FIFO\n");
/* V 操作信号量 full 和 mutex */
sem_post(&mutex);
sem_post(&full);
sleep(1);
}
}
void consumer(void *arg)
{
int ret,nread;
while(time(NULL) < end_time){
/* P 操作信号量 full 和 mutex */
sem_wait(&full);
sem_wait(&mutex);
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100)) == -1){
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
/* V 操作信号量 avail 和 mutex */
sem_post(&mutex);
sem_post(&avail);
sleep(1);
}
}