先知:
(1)线程是由进程创建而来,是cpu调度的最小单位。
(2)每个进程都有自己独立的地址空间,而进程中的多个线程共用进程的资源,他们只有自己独立的栈资源。
线程同步:
当多个控制线程共享相同的内存时,需要确保每个进程看到一致的数据视图。同一个数据如果被两个及以上的线程进行同时访问操作的时候,有可能就会造成数据不一致的现象。为了解决这个问题,线程不得不使用锁。同一时间只允许一个线程访问该变量。
比如增量操作:
(1)从内存单元读入寄存器
(2)在寄存器中对变量做增量操作
(3)把新值写回内存单元
如果两个线程几乎是同一时间对同一个变量做增量操作而不进行同步的话,结果就有可能出现数据不一致,变量有可能增加1了,也有可能比原来增加2了,具体是增加1了还是增加2了取决于第二个线程开始操作时候获取的数值。
线程同步机制(1):互斥量
什么是互斥量:
互斥量的本质就是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞知道线程释放该互斥量。
互斥量代码:
#include
#include
int count = 0;
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
void* thread_run1(void* arg)
{
int i = 0;
int val = 0;
for(;i < 5000;i++)
{
//进临界区时加锁
pthread_mutex_lock(&mylock);
//临界区
val = count;
printf("tid : %lu,count : %d\n",pthread_self(),count);
count = val + 1;
//出临界区时解锁
pthread_mutex_unlock(&mylock);
}
}
void* thread_run2(void* arg)
{
int i = 0;
int val = 0;
for(;i < 5000;i++)
{
//进临界区时加锁
pthread_mutex_lock(&mylock);
//临界区
val = count;
printf("tid : %lu,count : %d\n",pthread_self(),count);
count = val + 1;
//出临界区时解锁
pthread_mutex_unlock(&mylock);
}
}
int main()
{
printf("Lock...\n");
//线程ID
pthread_t tid1;
pthread_t tid2;
//创建线程
pthread_create(&tid1, NULL, thread_run1, NULL);
pthread_create(&tid2, NULL, thread_run2, NULL);
//等待新线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("count = %d\n",count);
//销毁锁
pthread_mutex_destroy(&mylock);
return 0;
}
锁开销:
多线程的引入在访问共享资源的时候就必须要加锁,为了避免多次加锁或者多次解锁,都要多次判断加锁解锁的条件。而影响服务器的性能杀手之一就是锁开销。如果锁的粒度太粗,就会出现很多线程阻塞等待相同的锁,并不能改善并发性,如果锁的粒度太细,那么过多的锁开销会使系统性能受到影响,而且代码变得复杂。作为一个程序员,在需要满足锁的需求的情况下,在代码复杂性和性能之间找到确定的平衡。
线程的同步机制(2):读写锁
什么是读写锁:
读写锁和互斥量类似,不过读写锁允许更高的并行性。互斥量有两种状态,要么加锁,要么解锁,而且两种状态一次只能有一个线程进行访问。读写锁有3中状态,读模式下加锁,写模式下加锁,不加锁装填。一次只能有一个线程可以占有写模式下的读写锁,但是有多个进程可以同时占有写模式下的读写锁
适用场景:
读写锁非常适用于对数据结构的读次数远大于写次数的情况。
读写锁代码:
#include
#include
int val = 0;
pthread_rwlock_t myrw;
void* run_reader(void* arg)
{
while(1)
{
//sleep(1);
pthread_rwlock_rdlock(&myrw);//只读模式去加锁
printf("val = %d\n",val);
pthread_rwlock_unlock(&myrw);
}
}
void* run_writer(void* arg)
{
while(1)
{
sleep(1);
pthread_rwlock_wrlock(&myrw);//只写模式去加锁
val++;
pthread_rwlock_unlock(&myrw);
}
}
int main()
{
printf("rwlock...\n");
pthread_rwlock_init(&myrw, NULL);
pthread_t reader;
pthread_t writer;
pthread_create(&reader, NULL, run_reader, NULL);
pthread_create(&writer, NULL, run_writer, NULL);
pthread_join(reader, NULL);
pthread_join(writer, NULL);
pthread_rwlock_destroy(&myrw);
return 0;
}
线程同步机制(3):条件变量
什么是条件变量:
条件变量给多个线程提供了一个会合的场所。条件变量和互斥锁一起使用,允许线程以无竞争的方式等待特定条件的发生。条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。
结合使用条件变量和互斥量对线程之间进行同步:生产者消费者模型
#include
#include
#include
#include
typedef struct _node
{
int data;
struct n_ode *next;
}node_t, *node_p, **node_pp;
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;//初始化锁
pthread_cond_t mycond = PTHREAD_COND_INITIALIZER;//初始化条件变量
node_p AllocNode(int data)
{
node_p node = (node_p)malloc(sizeof(node_t));
if(NULL == node)
{
perror("malloc");
return NULL;
}
node->data = data;
node->next = NULL;
return node;
}
void InitList(node_pp _h)
{
*_h = AllocNode(0);
}
void PushFrond(node_p list,int data)
{
assert(list);
node_p new_node = AllocNode(data);
new_node->next = list->next;
list->next = new_node;
}
int IsEmpty(node_p list)
{
if( NULL == list->next)
{
return 1;
}
else
{
return 0;
}
}
void DelNode(node_p node)
{
assert(node);
free(node);
}
void PopFrond(node_p list,int* data)
{
assert(list);
assert(data);
if( IsEmpty(list) )
{
printf("list is empty...\n");
return ;
}
node_p delnode = list->next;
list->next = delnode->next;
*data = delnode->data;
DelNode(delnode);
}
void DestroyList(node_p list)
{
assert(list);
int data = 0;
while( !IsEmpty(list))
{
PopFrond(list,&data);
}
DelNode(list);
}
void ShowList(node_p list)
{
node_p node = list->next;
while(node)
{
printf("%d ",node->data);
node = node->next;
}
printf("\n");
}
void* thread_product(void* arg)
{
node_p head = (node_p)arg;
while(1)
{
usleep(123456);
pthread_mutex_lock(&mylock);
int data = rand()%10000;
PushFrond(head,data);
printf("product done,data is : %d\n",data);
pthread_mutex_unlock(&mylock);
//生产出一个产品以后,唤醒一个消费者,通知消费者来取产品
pthread_cond_signal(&mycond);//signal(唤醒一个),broadcast(唤醒多个)
}
}
void* thread_consumer(void* arg)
{
node_p node = (node_p)arg;
int data = 0;
while(1)
{
pthread_mutex_lock(&mylock);
if(IsEmpty(node))
{
//如果链表为空的话,那么等待生产者至少生产一个产品的时候,才去取产品
pthread_cond_wait(&mycond, &mylock);
}
PopFrond(node,&data);
printf("consumer done,data is : %d\n",data);
pthread_mutex_unlock(&mylock);
}
}
int main()
{
//用链表充当中间场所
node_p head = NULL;
InitList(&head);
//线程ID
pthread_t tid1;
pthread_t tid2;
//创建线程
int ret1 = pthread_create(&tid1, NULL, thread_product, (void*)head);//创建生产者线程
int ret2 = pthread_create(&tid2, NULL, thread_consumer, (void*)head);//创建消费者线程
if(ret1 < 0 || ret2 < 0)
{
perror("pthread_create");
return -1;
}
//等待线程
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
DestroyList(head);
pthread_mutex_destroy(&mylock);
pthread_cond_destroy(&mycond);
//node_p head = NULL;
//InitList(&head);
//int i = 0;
//for( ; i < 10; ++i)
//{
// PushFrond(head, i);
// ShowList(head);
// sleep(1);
//}
//int data = 0;
//for( ; i >= 5; --i)
//{
// PopFrond(head, &data);
// ShowList(head);
// sleep(1);
//}
//DestroyList(head);
//ShowList(head);
return 0;
}
线程同步机制(4):自旋锁
什么是自旋锁:
自旋锁和互斥量类似,但是它不是通过休眠使线程阻塞,而是在获得锁之前一直处于忙等(自旋)阻塞状态。
适用场景:
锁被持有的时间短,而且线程并不希望在重新调度上花费大多的成本。等线程自旋等待变为可用时,cpu不能做其他的事情,这就是自旋锁只能被持有一小段时间的原因。
线程同步机制(5):屏障
屏障是什么:
屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,知道所有合作的线程都到达某一个点时,然后从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。屏障对象的概念就更加广泛了,他们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不用退出。所有的线程到达屏障以后可以接着工作。
屏障的应用之一:
比如说,现在有800万个数据要进行排序。现在创建出9个线程,一个主线程和8个工作线程。每个工作线程分别对100万个数据进行堆排序,主线程中设置屏障,等待八个线程完成数据的排序后,对八个线程排好序的八组数据在进行归并排序。在8核的系统中,单线程程序对800万个数进行排序需要12.14秒。同样的系统,使用8个并行线程和一个合并线程处理相同的800万个数排序仅仅只需要1.91秒,速度提高了6倍。