5. 两个线程同时执行
程序实例如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *my_thread(void *arg)
{
int i = 10;
while (i--) {
printf("BBB\n");
sleep(1);
}
pthread_exit(NULL);
}
int main(void)
{
pthread_t thread_id;
int i = 10;
if (pthread_create(&thread_id, NULL, my_thread, NULL)) {
printf("Can't create a new thread!\n");
return -1;
}
while (i--) {
printf("AAA\n");
sleep(1);
}
if (pthread_join(thread_id, NULL)) {
printf("Wait a thread exit error!\n");
return -1;
}
return 0;
}
具体先打印"AAA"还是先打印"BBB",这就要看具体的进程调度算法,但肯定是交替打印,因为在打印一条信息之后调用了sleep函数,该函数将使线程进入睡眠,sleep函数原型如下:
unsigned int sleep(unsigned int seconds);
引用该函数需要包含头文件unistd.h,参数seconds为睡眠的秒数。
6. 线程同步问题
(1) 信号量
信号量使用sem_init函数做初始化,函数原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
引用该函数需要包含头文件semaphore.h,第一个参数为初始化的信号量对象,第二个参数为是否在多个进程中共享,0表示不共享,只在当前进程中使用,非0值表示在多个进程中共享该信号量,最后>一个参数为信号量的一个初始值。sem_init函数调用成功返回0,否则返回-1。
sem_post函数的作用是以原子操作的方式给信号量的值加1,函数原型如下:
int sem_post(sem_t *sem);
sem为信号量对象,函数调用成功返回0,否则返回-1。
sem_wait函数的作用是以原子操作的方式给信号量的值减1,函数原型如下:
int sem_wait(sem_t *sem);
sem_destroy函数同sem_init的作用刚好相反,它是对已经使用完的信号量做后续处理,函数原型如下:
int sem_destroy(sem_t *sem);
信号量实例如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#define N 100
sem_t mutex;
sem_t empty;
sem_t full;
char buf[N];
int count = 0;
void *producer(void *arg)
{
while (1) {
sem_wait(&mutex);
sem_wait(&empty);
buf[count++] = 'a';
printf("count = %d\n", count);
sem_post(&full);
sem_post(&mutex);
sleep(1);
}
}
void *consumer(void *arg)
{
while (1) {
sem_wait(&mutex);
sem_wait(&full);
buf[--count] = '\0';
printf("count = %d\n", count);
sem_post(&empty);
sem_post(&mutex);
sleep(2);
}
}
int main(void)
{
pthread_t producer_id;
pthread_t consumer_id;
int ret = 0;
if (sem_init(&mutex, 0, 1)) {
printf("mutex semaphore initialization error!\n");
ret = -1;
goto err;
}
if (sem_init(&empty, 0, N)) {
printf("empty semphore initialization error!\n");
ret = -1;
goto err1;
}
if (sem_init(&full, 0, 0)) {
printf("full semphore initialization error!\n");
ret = -1;
goto err2;
}
if (pthread_create(&producer_id, NULL, producer, NULL)) {
printf("Can't create producer thread!\n");
ret = -1;
goto err3;
}
if (pthread_create(&consumer_id, NULL, consumer, NULL)) {
printf("Can't create consumer thread!\n");
ret = -1;
goto err3;
}
if (pthread_join(producer_id, NULL)) {
printf("Wait producer thread exit error!\n");
ret = -1;
goto err3;
}
if (pthread_join(consumer_id, NULL)) {
printf("Wait consumer thread exit error!\n");
ret = -1;
goto err3;
}
err3:
sem_destroy(&full);
err2:
sem_destroy(&empty);
err1:
sem_destroy(&mutex);
err:
return ret;
}
以上是一个生产者和消费的例子,在缓冲区已满的情况下,生产者不能往缓冲区写入数据,在缓冲区为空的情况下,消费者不能从缓冲区读走数据。这里用到了三个信号量,其中mutex信号量只有两种取值0和1,也称为二元信号量,用来确保生产者和消费者不会同时访问缓冲区,后面使用互斥锁也能实现同样的功能。empty信号量用来记录缓冲区剩余的空间大小,初值为100,full信号量用来记录缓冲区已有数据空间大小,初值为0,即一开始缓冲区没有数据,为空。信号量多用于数据的同步,例如这里的生产者与消费者,没有数据的时候,消费者是不能访问缓冲区的,而在缓冲区已满的情况下,生产者是不能访问缓冲区的。
还有一点需要注意的是,sem_wait会阻塞当前线程,直到信号量值不为0为止,如果需要非阻塞就使用sem_trywait函数。
(2) 互斥锁
互斥锁使用pthread_mutex_init做初始化,函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
加锁使用pthread_mutex_lock函数,函数原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
解锁使用pthread_mutex_unlock,函数原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
同sem_destroy函数一样,不再使用互斥锁时候需要使用pthread_mutex_destroy函数做后续的处理工作,函数原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
同Linux下大多函数的返回值一样,成功时返回0,失败时返回错误码。
前面就说过,可以使用互斥锁对缓冲区加保护,关键代码如下:
pthread_mutex_t mutex_lock;
void *producer(void *arg)
{
while (1) {
pthread_mutex_lock(&mutex_lock);
sem_wait(&empty);
buf[count++] = 'a';
printf("count = %d\n", count);
sem_post(&full);
pthread_mutex_unlock(&mutex_lock);
sleep(1);
}
}
void *consumer(void *arg)
{
while (1) {
pthread_mutex_lock(&mutex_lock);
sem_wait(&full);
buf[--count] = '\0';
printf("count = %d\n", count);
sem_post(&empty);
pthread_mutex_unlock(&mutex_lock);
sleep(2);
}
}
int main(void)
{
pthread_mutex_init(&mutex_lock, NULL);
...
}
互斥锁只有两种取值0和1,pthread_mutex_lock为阻塞版本,同信号量一样互斥锁也有非阻塞版本,函数为 pthread_mutex_trylock。