学习了apue3rd的第11章,主要讲的是多线程编程。因为线程共享进程的资源比如堆和全局变量,多线程编程最重要的是,使用各种锁进行线程同步。
线程编程首先要学习的三个函数如下:
#include <pthread.h>
int pthread_create(pthread_t* tidp, const pthread_attr_t* restrict attr, void* (*start rm)(void*), void* restrict arg)
这个函数是负责线程创建的。第一个参数是线程id,线程创建成功后,线程id将被写入tidp指向的内存。Linux下,pthread_t是unsigned long 类型。第二个参数是一个结构体指针,是传给线程的属性,决定了线程的很多行为,如果不使用,可以传一个NULL给attr。第三个是线程的起始函数,其必须是void* xxxxx(void* arg)类型,就是返回值和参数都是void* 指针。第四个是传给启动函数的参数,启动之后,启动函数的参数arg的值等于pthread_create的arg的值。
void pthread_exit(void * rval_ptr)
这个函数可以将一个线程终止退出。rval_ptr是你设置的一个指针,它指向的内存可以保存你要返回的终止状态信息结构体。
int pthread_join(pthread_t thread, void** rval_ptr)
这个函数用于母线程回收其他线程的资源。rval_ptr指向的地址,将会写上从pthread_exit返回的指针的值,从而获取到终止状态结构体。
示例代码: gcc main.c -o main -lpthread
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void* thread1(void* arg)
{
pthread_exit((void*)2);
}
int main(int argc, char* argv[])
{
pthread_t tid1;
int res;
void* rval;
res = pthread_create(&tid1,NULL,thread1,NULL);
if(res!=0)
{
printf("thread creating failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_join(tid1,&rval);
if(res!=0)
{
printf("thread join failed\n");
exit(EXIT_FAILURE);
}
printf("thread exit code is %lu\n",(unsigned long)rval);//网上说无符号长整顿输出格式是//lu
return 0;
}
编译出现stray '\241' in program,出现原因是在word中打出的双引号,拷贝到文本文件中,与应该有的英文双引号不同,应该重新用英文输入法打双引号。EXIT_FAILURE的头文件为stdlib.h
互斥锁
互斥锁是类似posix信号量的线程同步手段,其本质是通过原子操作来获取一个锁。其函数如下。
int pthread_mutex_init(pthread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr)
这个函数是在使用互斥锁之前先初始化一下锁。其参数分别是互斥锁结构体和互斥锁属性结构体的指针。
int pthread_mutex_destroy(pthread_mutex_t* mutex)
这个函数是使用完之后,销毁互斥锁。
int pthread_mutex_lock(pthread_mutex_t* mutex)
int pthread_mutex_unlock(pthread_mutex_t* mutex)
这两个函数分别是获取锁和释放锁。
应该注意到是:如果一个线程已经获取到一个锁,并继续获取这个锁,就会产生死锁。
如果一个线程已经释放了一个锁,再次释放那个锁不会产生死锁,但在销毁锁的时候会出错。
互斥锁只存在于进程中,并不存在于内核中,因此,一个进程的锁如果没有销毁而就结束的话,内核不会有前面留下的锁。
int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex,const struct timespec* restrict tsptr)
这个函数在获取锁的时候,可以避免发生死锁,如果到一个时间段获取不到锁,函数就会返回,返回码是EIMEDOUT。注意这里的tsptr是绝对时间,即从UTC1970-1-1 0:0:0开始计时的秒数和纳秒数。使用的时候需要使用clock_gettime获取系统绝对时间。然后再加上一个时间间隔,
gcc main.c -o main -lpthread -lrt -lrt存在的原因是使用了clock_gettime,需要链接库rt。注意这里的”-”符号粘贴到Linux时候有时会出错,需要使用英文输入法重新打“-“。
使用时候一般把锁定义为全局变量,方便各个线程使用。测试代码如下(在上面代码基础上修改):
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
pthread_mutex_t mutex1;
void* thread1(void* arg)
{
int res;
struct timespec tout; //定义在time.h
memset(&tout, 0, sizeof(tout));
clock_gettime(CLOCK_REALTIME, &tout);
tout.tv_sec += 10;
pthread_mutex_lock(&mutex1);
res = pthread_mutex_timedlock(&mutex1,&tout);
printf("timedlock return %d\n",res);
printf("%s\n",strerror(res)); //strerror在errno.h
//pthread_mutex_lock(&mutex1);
//pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex1);
pthread_exit((void*)2);
}
int main(int argc, char* argv[])
{
pthread_t tid1;
int res;
void* rval;
res = pthread_mutex_init(&mutex1,NULL); //NULL定义在stdio.h
if(res!=0)
{
printf("mutex init failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_create(&tid1,NULL,thread1,NULL);
if(res!=0)
{
printf("thread creating failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_join(tid1,&rval);
if(res!=0)
{
printf("thread join failed\n");
exit(EXIT_FAILURE);
}
printf("thread exit code is %lu\n",(unsigned long)rval);//网上说无符号长整顿输出格式是//lu
res = pthread_mutex_destroy(&mutex1);
if(res!=0)
{
printf("mutex destroy failed!\n");
exit(EXIT_FAILURE);
}
return 0;
}
读写锁
读写锁的使用与互斥锁非常类似,它适合的场景是有大量读,而只有少量写的情况,可以让更多的线程往前执行。
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,const pthread_rwlockattr_t* restrict attr)
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock)
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock)
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock)
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock)
自旋锁
自旋锁的时候与互斥锁类似,它适合的场景是线程每次执行的时间很短,其他线程不需要等多久的情况,自旋锁等待的时候,线程没有休眠,而是在不断的死循环。自旋锁适合的情况是自旋等待的开销比线程切换的开销小的情况。
int pthread_spin_init(pthread_spinlock_t* lock, int pshared)
int pthread_spin_destroy(pthread_spinlock_t* lock)
int pthread_spin_lock(pthread_spinlock_t* lock)
int pthread_spin_unlock(pthread_spinlock_t* lock )
int pthread_spin_trylock(pthread_spinlock_t* lock)
无论是互斥锁,或者是读写锁,或者自旋锁,或是以前学习的posix信号量,都存在一个问题,就是多个线程在竞争锁的时候,存在一个线程运行过快,从而反复获得锁,其他线程没有机会竞争到锁(复活线程需要时间)。需要在释放锁之后,空循环一定时间(5000-10000次),以便让其他线程充分时间获得锁。这是南京华为的面试官问我的问题。
gcc main.c -o main -lpthread –lrt
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
pthread_mutex_t mutex1;
pthread_spinlock_t spin1;
pthread_rwlock_t rwlock1;
void* thread1(void* arg)
{
int res;
struct timespec tout; //定义在time.h
memset(&tout, 0, sizeof(tout));
clock_gettime(CLOCK_REALTIME, &tout);
tout.tv_sec += 5;
pthread_mutex_lock(&mutex1);
res = pthread_mutex_timedlock(&mutex1,&tout);
printf("timedlock return %d\n",res);
printf("%s\n",strerror(res)); //strerror在errno.h
//pthread_mutex_lock(&mutex1);
//pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex1);
pthread_spin_lock(&spin1);
pthread_spin_unlock(&spin1);
pthread_rwlock_rdlock(&rwlock1);
pthread_rwlock_unlock(&rwlock1);
pthread_rwlock_wrlock(&rwlock1);
pthread_rwlock_unlock(&rwlock1);
pthread_exit((void*)2);
}
int main(int argc, char* argv[])
{
pthread_t tid1;
int res;
void* rval;
res = pthread_mutex_init(&mutex1,NULL); //NULL定义在stdio.h
if(res!=0)
{
printf("mutex init failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_spin_init(&spin1,PTHREAD_PROCESS_PRIVATE);
if(res!=0)
{
printf("spin init failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_rwlock_init(&rwlock1,NULL);
if(res!=0)
{
printf("rwlcok init failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_create(&tid1,NULL,thread1,NULL);
if(res!=0)
{
printf("thread creating failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_join(tid1,&rval);
if(res!=0)
{
printf("thread join failed\n");
exit(EXIT_FAILURE);
}
printf("thread exit code is %lu\n",(unsigned long)rval);//网上说无符号长整顿输出格式是//lu
res = pthread_mutex_destroy(&mutex1);
if(res!=0)
{
printf("mutex destroy failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_spin_destroy(&spin1);
if(res!=0)
{
printf("spin destroy failed!\n");
exit(EXIT_FAILURE);
}
res = pthread_rwlock_destroy(&rwlock1);
if(res!=0)
{
printf("rwlock destroy failed!\n");
exit(EXIT_FAILURE);
}
return 0;
}