在linux中用c语言实现线程(LinuxC多线程编程)
在linux中用c语言实现线程(LinuxC多线程编程)int pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_init(pthread_mutex_t *restrict mutex const pthread_mutexattr_t *restric attr);对于静态分配的互斥量 可以把它设置为PTHREAD_MUTEX_INITIALIZER 或者调用pthread_mutex_init.对于动态分配的互斥量 在申请内存(malloc)之后 通过pthread_mutex_init进行初始化 并且在释放内存(free)前需要调用pthread_mutex_destroy.原型:
一、互斥锁
互斥量从本质上说就是一把锁 提供对共享资源的保护访问。
1) 初始化:
在Linux下 线程的互斥量数据类型是pthread_mutex_t. 在使用前 要对它进行初始化:
对于静态分配的互斥量 可以把它设置为PTHREAD_MUTEX_INITIALIZER 或者调用pthread_mutex_init.
对于动态分配的互斥量 在申请内存(malloc)之后 通过pthread_mutex_init进行初始化 并且在释放内存(free)前需要调用pthread_mutex_destroy.
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex const pthread_mutexattr_t *restric attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
头文件:#include<pthread.h>
返回值: 成功则返回0 出错则返回错误编号.
说明: 如果使用默认的属性初始化互斥量 只需把attr设为NULL. 其他值在以后讲解。
2) 互斥操作:
对共享资源的访问 要对互斥量进行加锁 如果互斥量已经上了锁 调用线程会阻塞 直到互斥量被解锁. 在完成了对共享资源的访问后 要对互斥量进行解锁。
首先说一下加锁函数:
解除锁定互斥锁。
- 注 –
- 如果针对以前初始化的但尚未销毁的互斥锁调用 pthread_mutex_init(),则该互斥锁不会重新初始化。
- 如果属主无法使状态保持一致,请勿调用 pthread_mutex_init(),而是解除锁定该互斥锁。在这种情况下,所有等待的线程都将被唤醒。以后对 pthread_mutex_lock() 的所有调用将无法获取互斥锁,并将返回错误代码 ENOTRECOVERABLE。现在,通过调用pthread_mutex_destroy() 来取消初始化该互斥锁,即可使其状态保持一致。调用 pthread_mutex_init() 可重新初始化互斥锁。
- 如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主将获取该锁及错误代码 EOWNERDEAD。
- PTHREAD_PRIO_PROTECT
- 当线程拥有一个或多个使用 PTHREAD_PRIO_PROTECT 初始化的互斥锁时,此协议值会影响其他线程(如 thrd2)的优先级和调度。thrd2 以其较高的优先级或者以 thrd2 拥有的所有互斥锁的最高优先级上限运行。基于被 thrd2 拥有的任一互斥锁阻塞的较高优先级线程对于 thrd2 的调度没有任何影响。
如果某个线程调用 sched_setparam() 来更改初始优先级,则调度程序不会采用新优先级将该线程移到调度队列末尾。
- 线程拥有使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥锁
- 线程解除锁定使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥锁
一个线程可以同时拥有多个混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。
pthread_mutexattr_setprioceiling设置互斥锁的优先级上限
1 int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr int prioceiling int oldceiling);
attr 指示以前调用 pthread_mutexattr_init() 时创建的互斥锁属性对象。
prioceiling 指定已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling 位于 SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将 prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。
oldceiling 包含以前的优先级上限值。
pthread_mutexattr_getprioceiling获取互斥锁的优先级上限
1 int pthread_mutex_getprioceiling(const pthread_mutex_t *mutex int *prioceiling);
注 –
仅当定义了 _POSIX_THREAD_PRIO_PROTECT 符号时,attr 互斥锁属性对象才会包括优先级上限属性。
pthread_mutexattr_setrobust_np设置互斥锁的强健属性
1 int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr int robustness);
注 –
仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_setrobust_np() 才适用。
attr 指示以前通过调用 pthread_mutexattr_init() 创建的互斥锁属性对象。
robustness 定义在互斥锁的属主失败时的行为。pthread.h 中定义的 robustness 的值为 PTHREAD_MUTEX_ROBUST_NP 或PTHREAD_MUTEX_STALLED_NP。缺省值为 PTHREAD_MUTEX_STALLED_NP。
- PTHREAD_MUTEX_ROBUST_NP
- 如果互斥锁的属主失败,则以后对 pthread_mutex_lock() 的所有调用将以不确定的方式被阻塞。
- PTHREAD_MUTEX_STALLED_NP
- 互斥锁的属主失败时,将会解除锁定该互斥锁。互斥锁的下一个属主将获取该互斥锁,并返回错误 EOWNWERDEAD。
- 注 –
- 应用程序必须检查 pthread_mutex_lock() 的返回代码,查找返回错误 EOWNWERDEAD 的互斥锁。
- 互斥锁的新属主应使该互斥锁所保护的状态保持一致。如果上一个属主失败,则互斥锁状态可能会不一致。
- 如果新属主能够使状态保持一致,请针对该互斥锁调用 pthread_mutex_consistent_np(),并解除锁定该互斥锁。
- 如果新属主无法使状态保持一致,请勿针对该互斥锁调用 pthread_mutex_consistent_np(),而是解除锁定该互斥锁。
- 所有等待的线程都将被唤醒,以后对 pthread_mutex_lock() 的所有调用都将无法获取该互斥锁。返回代码为 ENOTRECOVERABLE。通过调用 pthread_mutex_destroy() 取消对互斥锁的初始化,并调用 pthread_mutex_int() 重新初始化该互斥锁,可使该互斥锁保持一致。
- 如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主获取该锁时将返回代码 EOWNERDEAD。
pthread_mutexattr_getrobust_np设置互斥锁的强健属性
1 int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr int *robustness);
注 –
仅当定义了符号 _POSIX_THREAD_PRIO_INHERIT 时,pthread_mutexattr_getrobust_np() 才适用。
attr 指示以前通过调用 pthread_mutexattr_init() 创建的互斥锁属性对象。
robustness 是互斥锁属性对象的强健属性值。
下面给个测试小程序进一步了解互斥,mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量testlock 执行了第一次pthread_mutex_lock(testlock)之后,在unlock(testlock)之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock(testlock),这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此,实现同步,也就达到保护临界区资源的目的。
1 #include<stdio.h> 2 #include<pthread.h> 3 4 static pthread_mutex_t testlock; 5 pthread_t test_thread; 6 7 void *test() 8 { 9 pthread_mutex_lock(&testlock); 10 printf("thread Test() \n"); 11 pthread_mutex_unlock(&testlock); 12 } 13 14 int main() 15 { 16 pthread_mutex_init(&testlock NULL); 17 pthread_mutex_lock(&testlock); 18 19 printf("Main lock \n"); 20 21 pthread_create(&test_thread NULL test NULL); 22 sleep(1); //更加明显的观察到是否执行了创建线程的互斥锁 23 printf("Main unlock \n"); 24 pthread_mutex_unlock(&testlock); 25 26 sleep(1); 27 pthread_join(test_thread NULL); 28 pthread_mutex_destroy(&testlock); 29 return 0; 30 } 31 32 make 33 gcc -D_REENTRANT -lpthread -o test test.c 34 35 结果: 36 Main lock 37 Main unlock 38 thread Test()
二、条件变量
条件变量用来阻塞线程等待某个事件的发生,并且当等待的事件发生时,阻塞线程会被通知。
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
条件变量的结构为pthread_cond_t
最简单的使用例子:
1 pthread_cond_t cond; 2 pthread_cond_init(&cond NULL); 3 pthread_cond_wait(&cond &mutex); 4 ... 5 pthread_cond_signal(&cond);
1)创建和注销
条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态方式调用pthread_cond_init()函数,API定义如下:
1 int pthread_cond_init(pthread_cond_t *cond pthread_condattr_t *cond_attr)
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。API定义如下:
1 int pthread_cond_destroy(pthread_cond_t *cond)
2)等待和激发
等待:
1 int pthread_cond_wait(pthread_cond_t *cond pthread_mutex_t *mutex) 2 int pthread_cond_timedwait(pthread_cond_t *cond pthread_mutex_t *mutex const struct timespec *abstime)
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。 且进入wait时会把传入的互斥锁解锁。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。
mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。 执行pthread_cond_wait()时自动解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。
因此,全过程可以描述为:
(1)pthread_mutex_lock()上锁,
(2)pthread_cond_wait()等待,等待过程分解为为:解锁--条件满足--加锁
(3)pthread_mutex_unlock()解锁。
激发:
1 /* 唤醒一个等待该条件变量cond的线程 */ 2 int pthread_cond_signal (pthread_cond_t *cond); 3 /* 唤醒所有等待条件变量cond的线程 */ 4 int pthread_cond_broadcast (pthread_cond_t *cond);
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。 两者 如果没有等待的线程,则什么也不做。
属性设置:
/* 初始化条件变量属性对象 */ int pthread_condattr_init (pthread_condattr_t * attr); /* 销毁条件变量属性对象 */ int pthread_condattr_destroy (pthread_condattr_t * attr); /* 获取条件变量属性对象在进程间共享与否的标识 */ int pthread_condattr_getpshared (const pthread_condattr_t* attr int* pshared); /* 设置条件变量属性对象,标识在进程间共享与否 */ int pthread_condattr_setpshared (pthread_condattr_t* attr int pshared) ;
PTHREAD_PROCESS_PRIVATE
如果条件变量的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该条件变量。
PTHREAD_PROCESS_SHARED
条件变量变量可以是进程专用的(进程内)变量,也可以是系统范围内的(进程间)变量。要在多个进程中的线程之间共享条件变量,可以在共享内存中创建条件变量,并将 pshared 属性设置为 PTHREAD_PROCESS_SHARED。
实例:
1 #include <pthread.h> 2 using namespace std; 3 4 pthread_cond_t qready = PTHREAD_COND_INITIALIZER; //初始构造条件变量 5 pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; //初始构造锁 6 pthread_t tid1 tid2 tid3; 7 8 int x = 10; 9 int y = 20; 10 11 12 void *thrd_1(void *arg) 13 { 14 pthread_mutex_lock(&qlock); 15 while(x<y) 16 { 17 pthread_cond_wait(&qready &qlock); 18 } 19 pthread_mutex_unlock(&qlock); 20 cout<<"1"<<endl; 21 sleep(5); 22 } 23 24 void *thrd_2(void *arg) 25 { 26 pthread_mutex_lock(&qlock); 27 x = 20; 28 y = 10; 29 cout<<"has change x and y"<<endl; 30 31 pthread_mutex_unlock(&qlock); 32 if(x > y) 33 { 34 pthread_cond_signal(&qready); 35 } 36 cout<<"2"<<endl; 37 } 38 39 void *thrd_3(void *arg) 40 { 41 pthread_join(tid1 NULL); 42 cout<<"3"<<endl; 43 } 44 45 int main(int argc char **argv) 46 { 47 int err; 48 err = pthread_create(&tid1 NULL thrd_1 NULL); 49 if(err != 0) 50 { 51 cout<<"pthread 1 create error"<<endl; 52 } 53 err = pthread_create(&tid2 NULL thrd_2 NULL); 54 if(err != 0) 55 { 56 cout<<"pthread 2 create error"<<endl; 57 } 58 err = pthread_create(&tid3 NULL thrd_3 NULL); 59 if(err != 0) 60 { 61 cout<<"pthread 3 create error"<<endl; 62 } 63 while(1) 64 { 65 sleep(1); 66 } 67 return 0; 68 69 }
可以看到,创建了3个线程后,执行顺序2,1,3,即打印出的数字是213。为什么是这个顺序呢?我们接下去看,当创建tid1线程的时候,进入线程函数,并且加上了锁,然后进入pthread_cond_wait函数,这个函数的功能是等待qready这个条件变量成功,这个条件是什么呢?我们稍后在看,现在我们只要知道,这个函数在qready条件没满足的时候会卡在这里,并且,会把传入的互斥锁解锁,为什么要解锁的,拟可以想想,如果不解锁的话,那外部就没有可以对x,y的修改权,应为其他两个线程想要修改这两个值的话都需要对qclock进行枷锁。
好了线程1就这样,那之后就会运行线程2,我们看线程2的线程函数,该函数一开始也加了锁,但当线程1的pthread_cond_wait解锁之后,他就可以继续运行了,并且,在之后,它对x,y进行了修改,改好之后进行了解锁,并且调用了pthread_cond_signal通知线程1,现在可以知道了吧。这个满足的条件就是要x>y。
现在这里有个问题,一定要在发送通知之前解锁吗?答案是肯定的,为什么,因为如果先发送通知信号给线程1的时候,pthread_cond_wait可能在线程2的解锁之前就返回,而当它返回的时候,会再次将这个所进行锁定,而这个所还没有在线程2中解锁,应次会使其在次卡住。虽然这个卡住在线程2运行到解锁处会消除,但这并不符合我们有时的需求,所以最好还是在解锁之后在发送信号。(如果看不懂的话,可以参考下面红色字体的部分!!!)
所以可以看出为什么线程2总是在线程1之前执行完毕,线程3就更不用说了,pthread_join你们懂的!!!
三、信号量
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。
只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数
pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。
下面我们逐个介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中定义。
信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
extern int sem_init __P ((sem_t *__sem int __pshared unsigned int __value));
sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
函数sem_destroy(sem_t *sem)用来释放信号量sem。
在上面的生产者、消费者例子中,我们假设缓冲区最多能放10条消息。用信号量来实现如下:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> static char buff[50]; pthread_mutex_t mutex; pthread_mutex_t cond_mutex; pthread_cond_t cond; sem_t msg_cnt; //缓冲区消息数 sem_t space_cnt; //缓冲区空闲数 void consumeItem(char *buff) { printf("consumer item\n"); } void produceItem(char *buff) { printf("produce item\n"); } void *consumer(void *param) { while (1) { sem_wait(&msg_cnt); pthread_mutex_lock(&mutex); consumeItem(buff); pthread_mutex_unlock(&mutex); sem_post(&space_cnt); } return NULL; } void *producer(void *param) { while (1) { sem_wait(&space_cnt); pthread_mutex_lock(&mutex); produceItem(buff); pthread_mutex_unlock(&mutex); sem_post(&msg_cnt); } return NULL; } int main() { pthread_t tid_c tid_p; void *retval; pthread_mutex_init(&mutex NULL); pthread_mutex_init(&cond_mutex NULL); pthread_cond_t cond; pthread_cond_init(&cond NULL); sem_init(&msg_cnt 0 0); //初始缓冲区没有消息 sem_init(&space_cnt 0 10); //初始缓冲区能放10条消息 pthread_create(&tid_p NULL producer NULL); pthread_create(&tid_c NULL consumer NULL); pthread_join(tid_p &retval); pthread_join(tid_c &retval); return 0; }