多线程编程
线程按照其调度者可以分为用户级线程和核心级线程两种。
(1)用户级线程
如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程中的其他 所有线程也同时被阻塞。这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无 法发挥多处理器的优势。
(2)核心级线程
这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥 多处理器的并发优势。
int pthread_create ((pthread_t *thread, pthread_attr_t *attr, void *(* start_routine)(void *), void *arg))
start_routine是线程函数的起始地址
void pthread_exit(void *retval)
retval是调用者线程的返回值,即使用不调用pthread_exit(),线程函数执行完之后线程也会自动退出,就像int main()即使最后没有return语句程序也会自动退出一样。
int pthread_join ((pthread_t th, void **thread_return))
thread_return:用户定义的指针,用来存储被等待线程的返回值(不为 NULL 时)
pthread_create 函数的第二个参数--线程的属性,将该值设为 NULL,也就是采用默认属性,线程的多项属性都是可以更改的。这些属性主要包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。其中系统默认的属性为非绑定、非分离、缺省 1M 的堆栈、与父进程同样级别的优先级。
• 绑定属性
Linux 中一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程, 非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。
CPU 时间片的调度是面向内核线程(也就是轻量级进程)的。
• 分离属性
分离属性是用来决定一个线程以什么样的方式来终止自己。在非分离情况下,当一个线程结束时, 它所占用的系统资源并没有被释放, 也就是没有真正的终止。只有当 pthread_join() 函数返回时,创建的线程才能释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。这里要注意的一点是,如果设置一个线程的分离属性, 而这个线程运行又非常快,那么它很可能在pthread_create 函数返回之前就终止了, 它终止以后就可能将线程号和系统资源移交给其他的线程使用, 这时调用 pthread_create 的线程就得到了错误的线程号。
struct sched_param
{
int sched_priority;
};
#include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<sys/types.h> void run1(void){ int i; for(i=0;i<6;++i){ puts("This is thread1."); if(i==2){ puts("thread1 exit."); pthread_exit(0); //调用者线程返回0 } sleep(1); } } void run2(void){ int i; for(i=0;i<2;++i){ puts("this is thread2."); sleep(1); } pthread_exit(0); } int main(){ pthread_t p1,p2; int ret; pthread_attr_t attr1,attr2; struct sched_param prio; //初始化线程属性 pthread_attr_init(&attr1); pthread_attr_init(&attr2); pthread_attr_setscope(&attr1,PTHREAD_SCOPE_SYSTEM); //设置绑定属性 pthread_attr_setscope(&attr2,PTHREAD_SCOPE_PROCESS); //设置非绑定属性 pthread_attr_setdetachstate(&attr1,PTHREAD_CREATE_DETACHED); //设置分离属性 pthread_attr_setdetachstate(&attr2,PTHREAD_CREATE_JOINABLE); //设置非分离属性 pthread_attr_getschedparam(&attr2,&prio); //获得线程优先级 --(prio.sched_priority); //优先级减1,相当于提高了优先级 pthread_attr_setschedparam(&attr2,&prio); //设置线程优先级 pthread_attr_getschedparam(&attr1,&prio); ++(prio.sched_priority); pthread_attr_setschedparam(&attr1,&prio); ret=pthread_create(&p1,&attr1,(void*)run1,NULL); if(ret==-1){ perror("thread_create"); exit(1); } //创建线程 ret=pthread_create(&p2,&attr2,(void*)run2,NULL); if(ret==-1){ perror("pthread_create"); exit(1); } //等待线程结束 pthread_join(p1,NULL); pthread_join(p2,NULL); return 0; }
1.mutex互斥锁线程控制
若一个线程希望上锁一个已经上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁。
互斥锁的操作主要包括以下几个步骤。
• 互斥锁初始化:pthread_mutex_init
• 互斥锁上锁: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)
其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望得到互斥锁时的是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够成功地返回并且增加调用线程在互斥上加锁的次数,而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
参数Mutexattr 的取值:
PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP: 创建检错互斥锁
2.信号量线程控制
#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value) /*创建并初始化信号量*/
sem:信号量
pshared:决定信号量能否在几个进程间共享。由于目前 Linux 还没有实现进程间共享信号量,所以这个值只能够取 0
value:信号量初始化值
#include <pthread.h>
int sem_wait(sem_t *sem) /*相当于P操作,若信号量小于0,则会阻塞进程*/
int sem_trywait(sem_t *sem) /*相当于P操作,若信号量小于0,则函数会立即返回*/
int sem_post(sem_t *sem) /*相当于V操作*/
int sem_getvalue(sem_t *sem) /*得到信号量的值*/
int sem_destroy(sem_t *sem) /*删除信号量*/
同样可以用信号量同步的机制实现上面的功能:
本文来自博客园,作者:高性能golang,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/1943103.html